C# Dispatcher.BeginInvoke()未异步运行
以下是我想做的一个简化版本: 单击C# Dispatcher.BeginInvoke()未异步运行,c#,wpf,asynchronous,task,dispatcher,C#,Wpf,Asynchronous,Task,Dispatcher,以下是我想做的一个简化版本: 单击按钮,将异步运行aNewMethod(),以保持UI响应。就这样 我读了一些答案,下面是我能想到的: private async void button_Click(object sender, RoutedEventArgs e) { Task task = Task.Run(() => aNewMethod()); await task; } private void aNewMetho
按钮
,将异步运行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;
}
}
这解决了系统.invalidoOperationException
问题,但它没有异步运行,因为UI仍然冻结在for loop
所以问题是:如何运行
aNewMethod()代码>异步且仍然更新并与UI控件交互?调度程序在UI线程中运行。它处理您的UI,并执行您通过BeginInvoke等传递给它的操作。
但它一次只能处理一件事;当它忙于处理您的操作时,它不会同时更新UI,因此UI会同时冻结
如果您想保持UI的响应性,您需要在一个单独的非UI线程中运行重载函数。在另一个线程上运行的那些函数中,您可以在需要访问UI时调用dispatcher—理想情况下,只是为了更新UI元素而非常简短
换句话说,您希望在单独的线程中运行sleep函数,然后在需要设置进度值时从自己的线程调用调度程序。差不多
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
,而应该使用类似的:
现在您不需要Dispatcher.Invoke
,而async
和await
关键字,因为await之后的语句将在我们进行异步调用的同一调用同步上下文中执行,在本例中,异步调用是UI线程
单击按钮后,aNewMethod()将异步运行以保持UI响应
实际上,它是在后台线程上同步运行的<代码>任务。运行
非常适合于此
通过谷歌搜索,我了解到我需要一个Dispatcher.BeginInvoke()方法来更新/使用UI控件
不幸的是,这是谷歌肯定会误导你的一个领域<代码>调度程序不是这里的最佳解决方案
相反,您应该使用IProgress
/Progress
,如下所示:
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);
}
private async void按钮\u单击(对象发送方,路由目标)
{
var progress=newprogress(value=>{this.progress.value=value;});
等待任务。运行(()=>aNewMethod(progress));
}
私有无效方法(IProgress progress)
{
//繁重的工作
对于(int i=1;i<100000000;i++){
进度报告(100);
}
useDispatcher.InvokeAsync
Sakura:我认为这实际上与Dispatcher.BeginInvoke是一样的。请注意,您正在尝试访问另一个线程中的UI线程对象,但这并不是全部。您明确要求在UI线程上运行Sleep
。不确定您还希望看到什么。旁注:您的示例有点误导性-要么一直使用async
,根本不需要BeginInvoke
,要么使用线程并使用BeginInvoke
手动调用UI线程。很公平-如果继续处理工作线程,您可能希望使用BeginInvoke之后,无需等待UI更新。。但是我已经将这个示例修改为对调度程序的同步调用,这可能更清楚。async
和await
的目的是消除这个调度程序。Invoke
,所以如果我们将它们与async
和await
一起使用,那么它就没有意义了
private async void button_Click(object sender, RoutedEventArgs e)
{
if (progress.Value == 0)
{
await Task.Delay(3000);
progress.Value = 100;
}
}
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);
}