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

This is related to my other question How to cancel background printing .

I am trying to better understand the CancellationTokenSource model and how to use it across thread boundaries.

I have a main window (in the user interface thread) where the code is located:

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

which correctly calls CloseWindow code when it is closed:

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

When you select a menu item in the background stream, a second window is created:

  // 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(); } } 

In MainWindowViewModel (in the UI thread) I put:

 public CancellationTokenSource windowClosingCTS { get; set; } 

With its constructor:

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

Now my problem. When MainWindow is closed in the user interface thread, windowClosingCTS.Cancel () calls an immediate delegate registered with ct, i.e. Called previewWindow.Close (). Now it immediately returns to the If (Windows! = Null) section with:

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

So what am I doing wrong?

+6
source share
2 answers

Your problem is that the preview window is working in a different thread. When you call a cancellation, you are performing the registered action of the cancellation token in this thread, and not in the thread that is previewing.

The gold standard in these cases is not to use two threads of the user interface. This usually causes problems, and the work you need to handle them is usually not worth it.

If you want to stay with your decision or want to call a cancellation from the background thread, you must march your close operation with the thread in which your window opens:

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

Problem with your code

When you select a menu item, a second window is created on the background stream:

 // 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(); } } 

And I believe that

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

The right decision is to do everything in one thread.

 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; } 

Then call

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

Source: https://habr.com/ru/post/1012968/


All Articles