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

时间:2022-01-22 10:48:50

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模式,而不是依赖(太多)代码。

3 个解决方案

#1


9  

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

所有的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 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 接口来报告进度:

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:

另一个需要考虑的常见用例是,如果用户自己关闭进度对话框,则取消操作。同样,如果您的操作已经使用了标准的取消令牌,这是很直接的:

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

上面的示例代码只使用数据绑定向progress对话框报告进度。如果您的MVVM框架可以数据绑定显示/隐藏一个模态窗口,那么您可以使用我的NotifyTaskCompletion类型来驱动它。此外,有些框架有一种更优雅的(MVVM)方法来处理窗口。关闭,但细节取决于您的框架。

#2


1  

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't attempt to open a dialog Window on a non UI thread.

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

Perhaps if you can clarify what your actual question is (by editing your question, not by commenting), then I can help further?

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

#3


0  

I think I have found a nearly working solution here: Create MVVM Background Tasks with Progress Reporting

我认为我在这里找到了一个几乎可以工作的解决方案:创建带有进度报告的MVVM后台任务

The only thing I have to get around with is the deactivation of the main window when showing the dialog.

我唯一要做的就是在显示对话框时关闭主窗口。

#1


9  

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

所有的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 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 接口来报告进度:

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:

另一个需要考虑的常见用例是,如果用户自己关闭进度对话框,则取消操作。同样,如果您的操作已经使用了标准的取消令牌,这是很直接的:

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

上面的示例代码只使用数据绑定向progress对话框报告进度。如果您的MVVM框架可以数据绑定显示/隐藏一个模态窗口,那么您可以使用我的NotifyTaskCompletion类型来驱动它。此外,有些框架有一种更优雅的(MVVM)方法来处理窗口。关闭,但细节取决于您的框架。

#2


1  

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't attempt to open a dialog Window on a non UI thread.

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

Perhaps if you can clarify what your actual question is (by editing your question, not by commenting), then I can help further?

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

#3


0  

I think I have found a nearly working solution here: Create MVVM Background Tasks with Progress Reporting

我认为我在这里找到了一个几乎可以工作的解决方案:创建带有进度报告的MVVM后台任务

The only thing I have to get around with is the deactivation of the main window when showing the dialog.

我唯一要做的就是在显示对话框时关闭主窗口。