Dispatcher.BeginInvoke() 不是 运行 异步

Dispatcher.BeginInvoke() not running asynchronously

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

onClick a button,a 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 说:

The calling thread cannot access this object because a different thread owns it.

谷歌搜索后,我了解到我需要一个 Dispatcher.BeginInvoke() 方法来 update/use 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 运行 在 UI 线程中。它处理您的 UI,并执行您通过 BeginInvoke 等传递给它的操作。 但它一次只能处理一件事;当它忙于处理您的操作时,它不会同时更新 UI,因此 UI 会同时冻结。

如果您想保持 UI 响应,您需要 运行 在单独的非 UI 线程中执行重负载功能。在另一个线程上 运行ning 的那些函数中,您可以在需要访问 UI 时调用调度程序 - 理想情况下,只是非常短暂地更新 UI 元素。

所以换句话说,您希望 运行在单独的线程中执行您的睡眠功能,然后在需要设置进度时从您自己的线程调用 Dispatcher价值。像

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 线程。

onClick a button, a aNewMethod() would run asynchronously to keep UI responsive.

实际上,它是 运行 同步 后台线程 上。 Task.Run 非常适合这个。

after some Googling, I've read that I need a Dispatcher.BeginInvoke() method to update/use UI controls

不幸的是,这是 Google 肯定会误导您的领域。 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);
}