【问题标题】:How to update GUI continuously with async如何使用异步持续更新 GUI
【发布时间】:2016-12-02 23:54:14
【问题描述】:

刚刚创建了一个WPF项目.net 4.6

并把这段代码放进去

lbl1 是 GUI 上的标签

但标签永远不会更新或while循环只继续1次

        private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        var t = Task.Run(
       async () =>
      {
           await AsyncLoop();
       });

    }

    async Task AsyncLoop()
    {
        while (true)
        {
            string result = await LoadNextItem();
            lbl1.Content = result;
        }
    }

    private static int ir11 = 0;
    async Task<string> LoadNextItem()
    {
        ir11++;
        return "aa " + ir11;
    }

【问题讨论】:

标签: c# wpf asynchronous async-await


【解决方案1】:

C# 编译器向您发出警告,告诉您问题所在。

具体来说,这个方法不是异步的:

async Task<string> LoadNextItem()
{
  ir11++;
  return "aa " + ir11;
}

编译器消息将通知您此async 方法没有await 语句,因此将同步运行。你应该只在有意义的地方使用async,通常用于基于 I/O 的操作,例如:

async Task<string> LoadNextItemAsync()
{
  await Task.Delay(100); // Placeholder for actual asynchronous work.
  ir11++;
  return "aa " + ir11;
}

或者,如果您没有有异步操作,而是有一些紧密的 CPU 绑定循环,那么您可以通过 Task.Run 将它们推送到后台线程:

string LoadNextItem()
{
  ir11++;
  return "aa " + ir11;
}

while (true)
{
  string result = await Task.Run(() => LoadNextItem());
  lbl1.Content = result;
}

【讨论】:

    【解决方案2】:

    通过调用 Task.Run,​​您破坏了与 GUI 线程(或 WPF Dispatcher)的关联(SynchronizationContext)并失去了大部分异步/等待“优点”。

    为什么不使用 async void 事件处理程序,而只为每个步骤返回 SynchronizationContext(GUI Thread/Dispatcher)?

    private async void Button_Click_1(object sender, RoutedEventArgs e)
    {
        while (true)
        {
            string result = await LoadNextItem();
            lbl1.Content = result;
        }
    }
    
    private static int ir11 = 0;
    Task<string> LoadNextItem()
    {
        await Task.Delay(1000); // placeholder for actual async work
        ir11++;
        return "aa " + ir11;
    }
    

    或者,如果您真的想为“正在进行的”操作分离状态机,请尝试传递一个 IProgress&lt;T&gt;(默认实现。Progress&lt;T&gt; 或特别是 Progress&lt;string&gt; 在这种情况下应该很好用)。见this article by @Stephen Cleary

    他的例子与你在问题中所说的非常接近。为了独立起见,我已将其复制到此处。

    public async void StartProcessingButton_Click(object sender, EventArgs e)
    {
      // The Progress<T> constructor captures our UI context,
      //  so the lambda will be run on the UI thread.
      var progress = new Progress<int>(percent =>
      {
        textBox1.Text = percent + "%";
      });
    
      // DoProcessing is run on the thread pool.
      await Task.Run(() => DoProcessing(progress));
      textBox1.Text = "Done!";
    }
    
    public void DoProcessing(IProgress<int> progress)
    {
      for (int i = 0; i != 100; ++i)
      {
        Thread.Sleep(100); // CPU-bound work
        if (progress != null)
          progress.Report(i);
      }
    }
    

    编辑:我必须承认,虽然Progress&lt;T&gt; 是一个很好的抽象,但在这种情况下,它只是按照@Pamparanpa 的建议归于 Dispatcher.Invoke。

    【讨论】:

      【解决方案3】:

      请用户 Application.Current.Dispatcher.Invoke 访问 ui 线程

      async Task AsyncLoop()
          {
              while (true)
              {
                  string result = await LoadNextItem();
      Application.Current.Dispatcher.Invoke(new Action(() => {  lbl1.Content = result; }));
      
              }
          }
      

      【讨论】:

      • 我明白了。所以它实际上是在抛出一个静默错误从而停止。有什么方法可以快速输入或复制粘贴? Application.Current.Dispatcher.Invoke(new Action(() => {
      • 是的,您可以直接在 wpf 中从另一个线程访问 ui 线程。
      • 这会等到 ui 更新吗?还是只是调度程序调用并立即返回?
      • 这是一个非阻塞操作
      • 这可行,但错过了对 async/await 的固有支持,以便在等待之后“返回”ui 线程。在上述函数中等待的唯一优点是在 LoadNextItem 上“等待”时不会占用线程池线程。这是相当大的,但在这种情况下并没有“完全”使用它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-29
      • 2014-04-16
      • 1970-01-01
      • 1970-01-01
      • 2012-03-20
      相关资源
      最近更新 更多