如何使用 CancellationTokenSource 关闭另一个线程上的对话框?

How to use CancellationTokenSource to close a dialog on another thread?

这与我的另一个问题How to cancel background printing有关。

我正在尝试更好地理解 CancellationTokenSource 模型以及如何跨线程边界使用它。

我有一个主要的 window(在 UI 线程上)后面的代码是:

 public MainWindow()
        {
            InitializeComponent();

            Loaded += (s, e) => {
                DataContext = new MainWindowViewModel();
                Closing += ((MainWindowViewModel)DataContext).MainWindow_Closing;

            };
        }

关闭时正确调用 CloseWindow 代码:

 private void CloseWindow(IClosable window)
        {
            if (window != null)
            {
                windowClosingCTS.Cancel();
                window.Close();
            }
        }

通过选择一个菜单项,第二个 window 在后台线程上创建:

    // Print Preview
    public static void PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
    {
        // Was cancellation already requested? 
        if (ct.IsCancellationRequested)
              ct.ThrowIfCancellationRequested();

               ............................... 

            // Use my custom document viewer (the print button is removed).
            var previewWindow = new PrintPreview(fixedDocumentSequence);

            //Register the cancellation procedure with the cancellation token
            ct.Register(() => 
                   previewWindow.Close() 
            );

            previewWindow.ShowDialog();

        }
    }

在 MainWindowViewModel(在 UI 线程上),我输入:

public CancellationTokenSource windowClosingCTS { get; set; }

其构造函数为:

    // Constructor
    public MainMenu()
    {
        readers = new List<Reader>();
        CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
        windowClosingCTS = new CancellationTokenSource();
    }

现在是我的问题。当关闭 UI 线程上的 MainWindow 时,windowClosingCTS.Cancel() 会立即调用在 ct 中注册的委托,即调用 previewWindow.Close()。 现在立即返回到“ If (Windows != null) with:

"The calling thread cannot access this object because a different thread owns it."

那我做错了什么?

你的代码有问题

With the selection of a menu item, a second window is created on a background thread:

// Print Preview
public static void PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
{
    // Was cancellation already requested? 
    if (ct.IsCancellationRequested)
          ct.ThrowIfCancellationRequested();

           ............................... 

        // Use my custom document viewer (the print button is removed).
        var previewWindow = new PrintPreview(fixedDocumentSequence);

        //Register the cancellation procedure with the cancellation token
        ct.Register(() => 
               previewWindow.Close() 
        );

        previewWindow.ShowDialog();

    }
}

我认为是

Task.Run(() => PrintPreview(foo, cancel));

正确的解决方案是在单个线程上执行所有操作。

public static Task<bool> PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
{
    var tcs = new TaskCompletionSource<bool>();
    // Was cancellation already requested? 
    if (ct.IsCancellationRequested)
          tcs.SetResult(false);
    else
    {
        // Use my custom document viewer (the print button is removed).
        var previewWindow = new PrintPreview(fixedDocumentSequence);

        //Register the cancellation procedure with the cancellation token
        ct.Register(() => previewWindow.Close());



        previewWindow.Closed += (o, e) =>
        {
             var result = previewWindow.DialogResult;
             if (result.HasValue)
                 tcs.SetResult(result.Value);
             else
                 tcs.SetResult(false);
         }
         previewWindow.Show();
    }

    return tcs.Task;
}

然后打电话给

 var shouldPrint = await PrintPreview(foo, cancel);
 if (shouldPrint)
     await PrintAsync(foo);

您的问题是您的预览 window 在另一个线程上运行。当您触发取消时,您将在该线程上执行取消令牌的注册操作,而不是在预览 运行 所在的线程上。

这些情况下的黄金标准是不使用 两个UI 线程。这通常会造成麻烦,而您需要处理它们的工作通常是不值得的。

如果您想保留您的解决方案或者如果您想从后台线程触发取消,您必须将您的关闭操作编组到您的 window 打开的线程:

Action closeAction = () => previewWindow.Close();
previewWindow.Dispatcher.Invoke(closeAction);