WPF - Task.Run(() => window.ShowDialog) 失败
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25619489/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF - Task.Run(() => window.ShowDialog) fails
提问by Yannik
I have to implement busy indication and progress reporting. The constraint is, that I have to use the provided Control Library, which offers a Window for progress reporting.
我必须实现忙碌指示和进度报告。限制是,我必须使用提供的控制库,它提供了一个用于进度报告的窗口。
The following code works fine, but does not block the UI, which in some times is required.
下面的代码工作正常,但不会阻塞 UI,这在某些时候是必需的。
private async void StartLongRunningTask2Sync() {
var wndHandle = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
if (wndHandle == null)
{
return;
}
IntPtr windowHandle = new WindowInteropHelper(wndHandle).Handle;
var progressWindow = new ProgressBarCustomized(windowHandle)
{
Value = 0, CanCancel = true, CanRetry = false, Thumbnail = null, IsIndeterminate = true
};
progressWindow.Show();
await Task.Run(() => this.longRunningTaskComponent.DoLongRunningTask(this.taskIterations, this.iterationSleepTime));
progressWindow.Close();
}
}
The following code, which blocks the UI would work so far that the dialog is opened, but the long running task never gets executed until the dialog is closed again:
以下代码会阻止 UI,直到对话框打开为止,但在对话框再次关闭之前,长时间运行的任务永远不会执行:
private async void StartLongRunningTask2Sync() {
var wndHandle = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
if (wndHandle == null)
{
return;
}
IntPtr windowHandle = new WindowInteropHelper(wndHandle).Handle;
var progressWindow = new ProgressBarCustomized(windowHandle)
{
Value = 0, CanCancel = true, CanRetry = false, Thumbnail = null, IsIndeterminate = true
};
progressWindow.ShowDialog();
await Task.Run(() => this.longRunningTaskComponent.DoLongRunningTask(this.taskIterations, this.iterationSleepTime));
progressWindow.Close();
}
}
So I tried with this approach:
所以我尝试了这种方法:
private async void StartLongRunningTask2Sync() {
var wndHandle = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
if (wndHandle == null)
{
return;
}
IntPtr windowHandle = new WindowInteropHelper(wndHandle).Handle;
var progressWindow = new ProgressBarCustomized(windowHandle)
{
Value = 0, CanCancel = true, CanRetry = false, Thumbnail = null, IsIndeterminate = true
};
Task.Run(() => progressWindow.ShowDialog());
await Task.Run(() => this.longRunningTaskComponent.DoLongRunningTask(this.taskIterations, this.iterationSleepTime));
progressWindow.Close();
}
}
When doing this, I get the following error: The calling thread cannot access this object because a different thread owns it.
执行此操作时,我收到以下错误:调用线程无法访问此对象,因为其他线程拥有它。
After investigation of the custom progress window I found out, that the call "base.ShowDialog()" throws this error.
在调查自定义进度窗口后,我发现调用“base.ShowDialog()”会引发此错误。
Is there a way to do what I like or do I have to do this with a totally different approach? Best regards
有没有办法做我喜欢做的事,或者我必须用完全不同的方法来做这件事?此致
UPDATE: Yes, I have searched for this error and yes, I have tried several approaches with Dispatcher.Invoke() etc...
更新:是的,我已经搜索过这个错误,是的,我已经尝试了几种使用 Dispatcher.Invoke() 等的方法......
So the "real" question: How can I show a blocking Window when a long running task is running and closing it after the long running task has finished and, eventually, inform the window about the progress of the action. The solution should (preferably) work with the MVVM pattern and not rely on (too much) code behind.
所以“真正”的问题是:如何在长时间运行的任务运行时显示阻塞窗口并在长时间运行的任务完成后关闭它,并最终通知窗口有关操作的进度。解决方案应该(最好)使用 MVVM 模式,而不是依赖(太多)背后的代码。
回答by Stephen Cleary
So the "real" question: How can I show a blocking Window when a long running task is running and closing it after the long running task has finished and, eventually, inform the window about the progress of the action.
所以“真正”的问题是:如何在长时间运行的任务运行时显示阻塞窗口并在长时间运行的任务完成后关闭它,并最终通知窗口有关操作的进度。
You've already got most of the pieces; you just need to put them together.
你已经得到了大部分的碎片;你只需要把它们放在一起。
How can I show a blocking Window
如何显示阻塞窗口
All UI should go on a single GUI thread. This isn't strictlynecessary, but it's a great simplifier and works for the vast, vast majority of applications. A "blocking Window" is known in the UI world as a "modal dialog", and you show one by calling ShowDialog.
所有 UI 都应该在一个 GUI 线程上运行。这不是绝对必要的,但它是一个很好的简化器,适用于绝大多数应用程序。“阻塞窗口”在 UI 世界中称为“模态对话框”,您可以通过调用ShowDialog.
// Start the long-running operation
var task = LongRunningOperationAsync();
// Show the dialog
progressWindow.ShowDialog();
// Retrieve results / propagate exceptions
var results = await task;
closing it after the long running task has finished
在长时间运行的任务完成后关闭它
For this, you need to wire up the completion of the task to close the window. This is pretty straightforward to do using async/await:
为此,您需要在完成任务时连线关闭窗口。使用async/非常简单await:
async Task DoOperationAsync(ProgressWindow progressWindow)
{
try
{
await LongRunningOperationAsync();
}
finally
{
progressWindow.Close();
}
}
// Start the long-running operation
var task = DoOperationAsync(progressWindow);
// Show the dialog
progressWindow.ShowDialog();
// Retrieve results / propagate exceptions
var results = await task;
inform the window about the progress of the action
通知窗口有关操作的进度
Assuming your operation is using the standard IProgress<T>interface for reporting progress:
假设您的操作使用标准IProgress<T>界面来报告进度:
async Task DoOperationAsync(Window progressWindow, IProgress<int> progress)
{
try
{
await LongRunningOperationAsync(progress);
}
finally
{
progressWindow.Close();
}
}
var progressWindowVM = ...;
var progress = new Progress<int>(value =>
{
progressWindowVM.Progress = value;
});
var task = DoOperationAsync(progressWindow, progress);
progressWindow.ShowDialog();
var results = await task;
Another common use case to consider is the cancelling of the operation if the user closes the progress dialog themselves. Again, this is straightfoward if your operation is already using the standard CancellationToken:
另一个需要考虑的常见用例是如果用户自己关闭进度对话框则取消操作。同样,如果您的操作已经在使用标准,这很简单CancellationToken:
async Task DoOperationAsync(Window progressWindow, CancellationToken token, IProgress<int> progress)
{
try
{
await LongRunningOperationAsync(token, progress);
}
catch (OperationCanceledException) { }
finally
{
progressWindow.Close();
}
}
var progressWindowVM = ...;
var progress = new Progress<int>(value =>
{
progressWindowVM.Progress = value;
});
var cts = new CancellationTokenSource();
progressWindow.Closed += (_, __) => cts.Cancel();
var task = DoOperationAsync(progressWindow, cts.Token, progress);
progressWindow.ShowDialog();
var results = await task;
The solution should (preferably) work with the MVVM pattern and not rely on (too much) code behind.
解决方案应该(最好)使用 MVVM 模式,而不是依赖(太多)背后的代码。
MVVM works great within a single window. As soon as you start trying to data-bind window-level actions and attributes, a lot of it falls apart. This is not due to MVVM being a poor pattern, but rather just that a lot of MVVM frameworks do not handle this well.
MVVM 在单个窗口中运行良好。一旦您开始尝试对窗口级操作和属性进行数据绑定,其中很多都会崩溃。这不是因为 MVVM 是一个糟糕的模式,而是因为许多 MVVM 框架不能很好地处理这个问题。
The example code above only uses data binding to report progress to the progress dialog. If your MVVM framework can data-bind the showing/hiding of a modal window, then you could use my NotifyTaskCompletiontypeto drive that. Also, some frameworks have a more elegant (MVVM) way to handle Window.Closed, but the details depend on your framework.
上面的示例代码仅使用数据绑定向进度对话框报告进度。如果您的 MVVM 框架可以对模式窗口的显示/隐藏进行数据绑定,那么您可以使用我的NotifyTaskCompletion类型来驱动它。此外,一些框架有更优雅的 (MVVM) 方式来处理Window.Closed,但细节取决于您的框架。
回答by Sheridan
The calling thread cannot access this object because a different thread owns it.
调用线程无法访问此对象,因为其他线程拥有它。
This is a very common error and if you had searched online, you would have found a very simple explanation.
这是一个非常常见的错误,如果您在网上搜索过,您会找到一个非常简单的解释。
You cannot manipulate UI objects on a non UI thread.
您不能在非 UI 线程上操作 UI 对象。
The solution is simple. Don'tattempt to open a dialog Windowon a non UI thread.
解决方法很简单。不要尝试Window在非 UI 线程上打开对话框。
Perhaps if you can clarify what your actualquestion is (by editing your question, not by commenting), then I can help further?
也许如果您可以澄清您的实际问题是什么(通过编辑您的问题,而不是通过评论),那么我可以进一步提供帮助吗?
回答by Yannik
I think I have found a nearly working solution here: Create MVVM Background Tasks with Progress Reporting
我想我在这里找到了一个几乎可行的解决方案: Create MVVM Background Tasks with Progress Reporting
The only thing I have to get around with is the deactivation of the main window when showing the dialog.
我唯一需要解决的是在显示对话框时停用主窗口。

