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

陌路散爱 提交于 2019-12-07 01:57:06

问题


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 (on the UI thread) where the code behind does:

 public MainWindow()
        {
            InitializeComponent();

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

            };
        }

which correctly calls the CloseWindow code when it is closed:

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

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

        }
    }

In the MainWindowViewModel (on the UI thread), I put:

public CancellationTokenSource windowClosingCTS { get; set; }

With its constructor of:

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

Now my problem. When closing the MainWindow on the UI thread, windowClosingCTS.Cancel() causes an immediate call to the delegate registered with ct, i.e. previewWindow.Close() is called. This now throws immediately back to the " If (Windows != null) with:

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

So what am I doing wrong?


回答1:


Your problem is that your preview window runs on another thread. When you trigger cancellation, you execute the registered action of the cancellation token on that thread, not on the thread your preview is running on.

The gold standard in these cases is to not use two UI threads. This will usually cause trouble and the work you need to handle them is usually not worth it.

If you want to stay with your solution or if you want to trigger cancellation from a background thread, you have to marshal your close operation to the thread your window is opened in:

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



回答2:


The problem with your code is

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

    }
}

And what I presume to be

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

The correct solution is to do everything on a single 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);


来源:https://stackoverflow.com/questions/41010412/how-to-use-cancellationtokensource-to-close-a-dialog-on-another-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!