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

这与我的其他问题如何取消后台打印有关 。

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

我有一个主窗口(在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(); } } 

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

  // 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(); CloseWindowCommand = new RelayCommand(this.CloseWindow); windowClosingCTS = new CancellationTokenSource(); } 

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

“调用线程无法访问此对象,因为不同的线程拥有它。”

那么我做错了什么?

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

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

如果您想继续使用您的解决方案,或者如果您想从后台线程触发取消,则必须将您的关闭操作编组到您的窗口打开的线程中:

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

您的代码存在问题

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

 // 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 PrintPreview(FixedDocument fixeddocument, CancellationToken ct) { var tcs = new TaskCompletionSource(); // 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);