Dispatcher.BeginInvoke() 未异步运行
本文关键字:异步 运行 BeginInvoke Dispatcher | 更新日期: 2023-09-27 18:36:35
这是我想要做的简化版本:
单击button
,aNewMethod()
将异步运行以保持UI响应。就是这样!
我已经阅读了一些答案,这是我能想到的:
private async void button_Click(object sender, RoutedEventArgs e)
{
Task task = Task.Run(() => aNewMethod());
await task;
}
private void aNewMethod()
{
if (progress.Value == 0)
{
//Heavy work
for (int i = 1; i < 1000000000; i++) { }
progress.Value = 100;
}
}
正如您可能已经想到的那样,这给if(progress.Value== 0)
说System.InvalidOperationException
调用线程无法访问此对象,因为不同的 线程拥有它。
经过一些谷歌搜索,我读到我需要一种Dispatcher.BeginInvoke()
方法来更新/使用 UI 控件,所以我这样做了:
private void aNewMethod()
{
Dispatcher.BeginInvoke((Action)delegate {
if (progress.Value == 0)
{
//Heavy work
for (int i = 1; i < 1000000000; i++) { }
progress.Value = 100;
}
});
}
这解决了System.InvalidOperationException
但它没有异步运行,因为 UI 仍然冻结在for loop
所以问题是:如何异步运行aNewMethod();
并仍然更新并与UI控件交互?
调度程序在 UI 线程中运行。它处理您的UI,并执行您通过BeginInvoke等传递给它的操作。但它一次只能处理一件事;当它忙于处理你的操作时,它不会同时更新 UI,因此 UI 在此期间冻结。
如果要保持 UI 响应,则需要在单独的非 UI 线程中运行重载函数。在另一个线程上运行的那些函数中,您可以在需要访问 UI 时调用调度程序 - 理想情况下,只是为了更新 UI 元素而非常简短。
因此,换句话说,您希望在单独的线程中运行睡眠函数,然后在需要设置进度值时从自己的线程调用调度程序。类似的东西
private void button_Click(object sender, RoutedEventArgs e)
{
Task task = Task.Run(() => aNewMethod()); // will call aNewMethod on the thread pool
}
private void aNewMethod()
{
double progressValue = 0;
Dispatcher.Invoke(() => progressValue = progress.Value);
if (progressValue == 0)
{
Thread.Sleep(3000); // still executes on the threadpool (see above), so not blocking UI
Dispatcher.Invoke(() => progress.Value = 100 ); // call the dispatcher to access UI
}
}
使用您当前的实现,无需使用 Thread.Start
,因为在该方法中,您只是让它休眠一段时间并访问那里的 UI 线程对象,这是不允许的.在您的方案中,更好的方法是不应使用Thread.Sleep
,而应使用如下Task.Delay
private async void button_Click(object sender, RoutedEventArgs e)
{
if (progress.Value == 0)
{
await Task.Delay(3000);
progress.Value = 100;
}
}
现在你不需要Dispatcher.Invoke
,功劳归于async
和await
关键字,因为 await 之后的语句将在我们执行异步调用的同一调用同步上下文中执行,在这种情况下是 UI 线程。
on单击一个按钮,aNewMethod() 将异步运行以保持 UI 响应。
实际上,它在后台线程上同步运行。 Task.Run
非常适合这样做。
经过一些谷歌搜索,我读到我需要一个 Dispatcher.BeginInvoke() 方法来更新/使用 UI 控件
不幸的是,这是谷歌肯定会误导你的一个领域。 Dispatcher
不是这里最好的解决方案。
相反,您应该使用 IProgress<T>
/Progress<T>
,如下所示:
private async void button_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<int>(value => { this.progress.Value = value; });
await Task.Run(() => aNewMethod(progress));
}
private void aNewMethod(IProgress<int> progress)
{
//Heavy work
for (int i = 1; i < 1000000000; i++) { }
progress.Report(100);
}