Dispatcher.BeginInvoke() 未异步运行

本文关键字:异步 运行 BeginInvoke Dispatcher | 更新日期: 2023-09-27 18:36:35

这是我想要做的简化版本:

单击buttonaNewMethod()将异步运行以保持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控件交互?

Dispatcher.BeginInvoke() 未异步运行

调度程序在 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,功劳归于asyncawait关键字,因为 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);
}