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

Ala*_*yne 6 c# wpf multithreading cancellationtokensource

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

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

我有一个主窗口(在UI线程上)后面的代码:

 public MainWindow()
        {
            InitializeComponent();

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

            };
        }
Run Code Online (Sandbox Code Playgroud)

它在关闭时正确调用CloseWindow代码:

 private void CloseWindow(IClosable window)
        {
            if (window != null)
            {
                windowClosingCTS.Cancel();
                window.Close();
            }
        }
Run Code Online (Sandbox Code Playgroud)

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

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

        }
    }
Run Code Online (Sandbox Code Playgroud)

在MainWindowViewModel(在UI线程上),我把:

public CancellationTokenSource windowClosingCTS { get; set; }
Run Code Online (Sandbox Code Playgroud)

其构造函数为:

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

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

"调用线程无法访问此对象,因为另一个线程拥有它."

那么我做错了什么?

Sef*_*efe 5

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

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

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

Action closeAction = () => previewWindow.Close();
previewWindow.Dispatcher.Invoke(closeAction);
Run Code Online (Sandbox Code Playgroud)

  • @AlanWayne:解决方案通常不是启动第二个 UI 线程,而是启动一个非 UI 工作线程。通常,从 UI 元素访问数据对性能并不重要,因此最好的方法是首先从 UI 元素同步收集数据,启动工作任务,然后将结果封送回 UI 线程以显示信息。您还可以做的是在计算方法中使用 async/await。当您有不支持 async/await 的方法(例如其他问题中的 XPS write 方法)时,您可以使用“TaskCompletionSource”(在那里查看我的答案)。 (2认同)