如何使用异步连续更新GUI

时间:2016-07-28 14:09:38

标签: c# wpf asynchronous async-await

刚刚创建了一个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;
    }

3 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

通过调用Task.Run,​​您破坏了与GUI线程(或WPF Dispatcher)的关联(SynchronizationContext),并丢失了大部分async / await&#39; goodness&#39;。

为什么不使用async void事件处理程序并返回每个步骤的SynchronizationContext(GUI线程/调度程序)?

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

或者,如果您真的想要将状态机分开,以便正常运行&#39;操作,尝试传递IProgress<T>(默认impl。Progress<T>或具体Progress<string>在这种情况下应该很好用)。见this article by @Stephen Cleary

他的例子与你在问题中所说的非常接近。我已经将它复制到这里以获得SO独立性。

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<T>是一个很好的抽象,但在这种情况下,它只会落到Dispatcher.Invoke上,如@Pamparanpa建议的那样。

答案 2 :(得分:1)

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

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

        }
    }