【问题标题】:Dispatcher.BeginInvoke() not running asynchronouslyDispatcher.BeginInvoke() 没有异步运行
【发布时间】:2016-06-10 22:55:24
【问题描述】:

这是我想做的简化版本:

onClick 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.InvokeAsync
  • Sakura:我觉得这和 Dispatcher.BeginInvoke 差不多,见stackoverflow.com/questions/13412670/…
  • 您正试图在另一个线程中访问 UI 线程对象,而该线程对象并非是 allwoed
  • 您明确要求 Sleep 在 UI 线程上运行。不知道你还能期待什么。

标签: c# wpf asynchronous task dispatcher


【解决方案1】:

单击按钮后,aNewMethod() 将异步运行以保持 UI 响应。

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

在谷歌搜索后,我了解到我需要一个 Dispatcher.BeginInvoke() 方法来更新/使用 UI 控件

很遗憾,这是 Google 肯定会误导您的一个领域。 Dispatcher 不是这里最好的解决方案。

相反,您应该使用IProgress&lt;T&gt;/Progress&lt;T&gt;,例如:

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);
}

【讨论】:

    【解决方案2】:

    使用您当前的实现,无需使用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 关键字,因为等待之后的语句将在我们执行异步调用的同一调用同步上下文中执行,这是UI 线程案例。

    【讨论】:

      【解决方案3】:

      Dispatcher 在 UI 线程中运行。它处理您的 UI,并执行您通过 BeginInvoke 等传递给它的操作。 但它一次只能处理一件事;当它忙于处理您的操作时,它不会同时更新 UI,因此 UI 会在此期间冻结。

      如果您想保持 UI 响应,则需要在单独的非 UI 线程中运行繁重的负载功能。在那些在另一个线程上运行的函数中,您可以在需要访问 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
          }
      }
      

      【讨论】:

      • 旁注:您的示例有点误导 - 要么一直使用async,你根本不需要BeginInvoke,要么使用线程并使用@987654324手动回调UI线程@.
      • 很公平 - 如果之后继续在工作线程上工作而不需要等待 UI 更新,您可能希望使用 BeginInvoke。但我已将此示例修改为同步调用给调度员,可能更清楚。
      • asyncawait 的目的是消除这个Dispatcher.Invoke,所以如果我们将它们与asyncawait 一起使用,那么它就没有意义了
      猜你喜欢
      • 2019-03-03
      • 2021-01-15
      • 1970-01-01
      • 1970-01-01
      • 2021-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-18
      相关资源
      最近更新 更多