WPF - Task.Run(()=> window.ShowDialog)失败

时间:2014-09-02 08:54:10

标签: wpf modal-dialog thread-safety

我必须实施忙碌指示和进度报告。限制是,我必须使用提供的控制库,它提供了一个用于进度报告的窗口。

以下代码可以正常工作,但不会阻止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();

}

以下阻止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();

}

所以我尝试了这种方法:

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

}

执行此操作时,我收到以下错误: 调用线程无法访问此对象,因为另一个线程拥有它。

在调查我发现的自定义进度窗口后,调用“base.ShowDialog()”会抛出此错误。

有没有办法做我喜欢或做的事情我必须用完全不同的方法做到这一点? 最好的问候

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

所以“真正的”问题: 如何在长时间运行的任务运行时显示阻塞窗口,并在长时间运行的任务完成后关闭它,并最终通知窗口有关操作进度的信息。解决方案应该(最好)使用MVVM模式,而不是依赖(太多)代码。

3 个答案:

答案 0 :(得分:12)

  

所以“真正的”问题:如何在长时间运行的任务运行时显示阻塞窗口,并在长时间运行的任务完成后关闭它,最终通知窗口该操作的进度。

你已经获得了大部分作品;你只需要将它们放在一起。

  

如何显示阻止窗口

所有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;
  

在长时间运行的任务完成后关闭它

为此,您需要连接完成任务以关闭窗口。使用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;
  

通知窗口有关操作的进度

假设您的操作使用标准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;

要考虑的另一个常见用例是,如果用户自己关闭进度对话框,则取消操作。同样,如果您的操作已使用标准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模式,而不是依赖(太多)代码。

MVVM在单个窗口中运行良好。一旦你开始尝试数据绑定窗口级别的操作和属性,它就会崩溃。这不是因为MVVM是一个糟糕的模式,而是因为很多MVVM框架都不能很好地处理这个问题。

上面的示例代码仅使用数据绑定将进度报告给进度对话框。如果您的MVVM框架可以数据绑定模式窗口的显示/隐藏,那么您可以使用我的NotifyTaskCompletion type来驱动它。此外,一些框架具有更优雅(MVVM)的方式来处理Window.Closed,但细节取决于您的框架。

答案 1 :(得分:1)

  

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

这是一个非常常见的错误,如果你在网上搜索过,你会发现一个非常简单的解释。

  

您无法在非UI线程上操作UI对象。

解决方案很简单。 不要尝试在非UI线程上打开对话框Window

也许如果你能澄清你的实际问题是什么(通过编辑你的问题,而不是通过评论),那么我可以进一步提供帮助吗?

答案 2 :(得分:0)

我想我在这里找到了一个近乎可行的解决方案: Create MVVM Background Tasks with Progress Reporting

我唯一需要解决的是在显示对话框时取消激活主窗口。