WPF MainWindow 即使在异步状态下也会冻结
WPF MainWindow freezes even in Asynchronous state
我想做的是执行由主窗口上的按钮事件触发的繁重任务,但仍然能够自由拖动 window。我已经尝试了 async/await 模式和创建新线程。但是,线程将是非阻塞的,MainWindow 仍然会冻结。这是代码:
uiTIN.Click += async (o, e) =>
{
var _ = await Task.Run(() => job());
};
这是按钮回调,这是函数:
private int job()
{
this.Dispatcher.Invoke(() =>
{
//Other function calls here omitted
});
return 0;
}
编辑: 解决方法是使用 BackgroundWorker,我还在 Dispatcher Invoke 函数中装饰了相关的 UI 代码片段
通常的做法是,您必须尽快从按钮的 onClick 事件回调中 return,以避免阻塞主线程(或某些参考 UI 线程)。如果主线程被阻塞,应用程序看起来就像冻结了一样。这是任何 GUI 应用程序同步 UI 流的基本设计。
您在回调中启动了一个异步任务,但您也在 returning 之前等待任务完成。您应该在 onClick 事件中启动 BackgroundWorker 然后 return。
来自 Dispatcher 上的 Microsoft's doccumentation(强调我的):
In WPF, a DispatcherObject
can only be accessed by the Dispatcher
it is associated with. For example, a background thread cannot update the contents of a Button
that is associated with the Dispatcher
on the UI thread. In order for the background thread to access the Content
property of the Button, the background thread must delegate the work to the Dispatcher
associated with the UI thread. This is accomplished by using either Invoke
or BeginInvoke
. Invoke
is synchronous and BeginInvoke
is asynchronous. The operation is added to the queue of the Dispatcher
at the specified DispatcherPriority
.
所以基本上你正在做的是调用一个异步方法,然后在 UI 线程上强制它 运行,这没有完成任何事情。
在你的 //Other function calls here omitted
中,我假设你需要访问 UI 的某些部分,如果不是这样,你所要做的就是删除 Dispatcher.Invoke
从你的方法。
如果我的假设是正确的,那么您必须想出一种拆分函数的方法,以便在后台线程中 UI 不相关的部分 运行 只有需要的部分运行 在 UI 线程上实际执行。
我的建议是使用 Background Worker
。它的外观如下:
uiTIN.Click += (o, e) =>
{
job();
};
...然后...
private int job()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
// Part of other function calls here omitted that don't need to run on the UI thread
Dispatcher.Invoke(() =>
{
// Part of other function calls here omitted that must run on the UI thread
});
};
worker.RunWorkerAsync();
return 0;
}
已经很好地解释了为什么您的代码阻塞了 UI 线程(在 Dispatcher 上排队您的工作)。但我不建议使用 BackgroundWorker
,我宁愿使用 Task.Run
修复您的代码,原因有多种,本文均有解释:https://blog.stephencleary.com/2013/09/taskrun-vs-backgroundworker-conclusion.html
我想做的是执行由主窗口上的按钮事件触发的繁重任务,但仍然能够自由拖动 window。我已经尝试了 async/await 模式和创建新线程。但是,线程将是非阻塞的,MainWindow 仍然会冻结。这是代码:
uiTIN.Click += async (o, e) =>
{
var _ = await Task.Run(() => job());
};
这是按钮回调,这是函数:
private int job()
{
this.Dispatcher.Invoke(() =>
{
//Other function calls here omitted
});
return 0;
}
编辑: 解决方法是使用 BackgroundWorker,我还在 Dispatcher Invoke 函数中装饰了相关的 UI 代码片段
通常的做法是,您必须尽快从按钮的 onClick 事件回调中 return,以避免阻塞主线程(或某些参考 UI 线程)。如果主线程被阻塞,应用程序看起来就像冻结了一样。这是任何 GUI 应用程序同步 UI 流的基本设计。
您在回调中启动了一个异步任务,但您也在 returning 之前等待任务完成。您应该在 onClick 事件中启动 BackgroundWorker 然后 return。
来自 Dispatcher 上的 Microsoft's doccumentation(强调我的):
In WPF, a
DispatcherObject
can only be accessed by theDispatcher
it is associated with. For example, a background thread cannot update the contents of aButton
that is associated with theDispatcher
on the UI thread. In order for the background thread to access theContent
property of the Button, the background thread must delegate the work to theDispatcher
associated with the UI thread. This is accomplished by using eitherInvoke
orBeginInvoke
.Invoke
is synchronous andBeginInvoke
is asynchronous. The operation is added to the queue of theDispatcher
at the specifiedDispatcherPriority
.
所以基本上你正在做的是调用一个异步方法,然后在 UI 线程上强制它 运行,这没有完成任何事情。
在你的 //Other function calls here omitted
中,我假设你需要访问 UI 的某些部分,如果不是这样,你所要做的就是删除 Dispatcher.Invoke
从你的方法。
如果我的假设是正确的,那么您必须想出一种拆分函数的方法,以便在后台线程中 UI 不相关的部分 运行 只有需要的部分运行 在 UI 线程上实际执行。
我的建议是使用 Background Worker
。它的外观如下:
uiTIN.Click += (o, e) =>
{
job();
};
...然后...
private int job()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
// Part of other function calls here omitted that don't need to run on the UI thread
Dispatcher.Invoke(() =>
{
// Part of other function calls here omitted that must run on the UI thread
});
};
worker.RunWorkerAsync();
return 0;
}
已经很好地解释了为什么您的代码阻塞了 UI 线程(在 Dispatcher 上排队您的工作)。但我不建议使用 BackgroundWorker
,我宁愿使用 Task.Run
修复您的代码,原因有多种,本文均有解释:https://blog.stephencleary.com/2013/09/taskrun-vs-backgroundworker-conclusion.html