如何使用 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);
这与我的另一个问题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);