WPF-Task.Run(()=> window.ShowDialog)失败 [英] WPF - Task.Run(() => window.ShowDialog) fails

查看:128
本文介绍了WPF-Task.Run(()=> window.ShowDialog)失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须执行繁忙的指示和进度报告.限制是,我必须使用提供的控制库,该库提供进度报告的窗口.

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.

以下代码可以正常工作,但不会阻止UI,这有时是必需的.

The following code works fine, but does not block the UI, which in some times is required.

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

}

以下阻止UI的代码可以正常工作,以至于打开对话框,但是长时间运行的任务直到再次关闭对话框时才执行:

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:

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.

调查自定义进度窗口后,我发现调用"base.ShowDialog()"会引发此错误.

After investigation of the custom progress window I found out, that the call "base.ShowDialog()" throws this error.

是否有一种方法可以做我喜欢的事情,或者我必须用一种完全不同的方法来做到这一点? 最好的问候

Is there a way to do what I like or do I have to do this with a totally different approach? Best regards

更新: 是的,我已经搜索了此错误,是的,我已经尝试使用Dispatcher.Invoke()等几种方法...

UPDATE: Yes, I have searched for this error and yes, I have tried several approaches with Dispatcher.Invoke() etc...

因此,真实"问题是: 当长时间运行的任务正在运行时,如何显示一个阻止窗口,如何在长时间运行的任务完成后关闭该窗口,并最终将操作进度通知给该窗口.该解决方案应该(最好)与MVVM模式一起使用,而不依赖于后面的(太多)代码.

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.

推荐答案

真正的"问题:当长时间运行的任务正在运行时,如何显示一个阻塞窗口,而在长时间运行的任务完成后如何关闭该窗口,并最终将该操作的进度告知该窗口.

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

所有UI都应在单个GUI线程上运行. 严格不是必需的,但这是一个很好的简化程序,适用于绝大多数应用程序.在UI世界中,阻止窗口"被称为模式对话框",您可以通过调用ShowDialog来显示一个窗口.

All UI should go on a single GUI thread. This isn't strictly necessary, 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.

// 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

为此,您需要连接完成任务以关闭窗口.使用async/await可以很简单地做到这一点:

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

假设您的操作使用标准的IProgress<T>界面报告进度:

Assuming your operation is using the standard IProgress<T> interface for reporting progress:

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;

要考虑的另一个常见用例是,如果用户自己关闭进度对话框,则取消操作.同样,如果您的操作已经使用标准的CancellationToken

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:

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;

该解决方案应该(最好)与MVVM模式一起使用,而不依赖于后面的(太多)代码.

The solution should (preferably) work with the MVVM pattern and not rely on (too much) code behind.

MVVM在单个窗口中运行良好.一旦您开始尝试对窗口级操作和属性进行数据绑定,那么其中的许多功能都将分崩离析.这不是由于MVVM模式不佳,而是由于许多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框架可以对模式窗口的显示/隐藏进行数据绑定,则可以使用我的

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 NotifyTaskCompletion type to drive that. Also, some frameworks have a more elegant (MVVM) way to handle Window.Closed, but the details depend on your framework.

这篇关于WPF-Task.Run(()=&gt; window.ShowDialog)失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆