wpf 如何使用异步连续更新 GUI

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38638911/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:53:22  来源:igfitidea点击:

How to update GUI continuously with async

c#wpfasynchronousasync-await

提问by MonsterMMORPG

Just created a WPF project .net 4.6

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

And have put this code inside

并把这段代码放在里面

lbl1is a label on the GUI

lbl1是 GUI 上的标签

But the label is never updated or the while loop continue only 1 time

但标签永远不会更新或 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;
    }

采纳答案by suulisin

Please user Application.Current.Dispatcher.Invoke to access the ui thread

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

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

        }
    }

回答by Stephen Cleary

The C# compiler is giving you a warning that tells you what the problem is.

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

Specifically, this method is not asynchronous:

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

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

The compiler message will inform you that this asyncmethod has no awaitstatements, and thus will run synchronously. You should only use asyncwhere it makes sense, usually for I/O-based operations, e.g.:

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

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

Alternatively, if you don'thave asynchronous operations, but rather you have some tight CPU-bound loops, then you can push those off to a background thread via Task.Run:

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

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

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

回答by SensorSmith

By invoking Task.Run you broke your association(SynchronizationContext) with the GUI thread (or WPF Dispatcher) and lost most of the async/await 'goodness'.

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

Why not use an async void event handler and just come back to the SynchronizationContext(GUI Thread/Dispatcher) for each step?

为什么不使用 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;
}

Or if you really want to separate the state machine for the 'on-going' operations, try passing an IProgress<T>(the default impl. Progress<T>or specifically Progress<string>should work great in this case). See this article by @Stephen Cleary

或者,如果您真的想为“正在进行的”操作分离状态机,请尝试传递一个IProgress<T>(默认实现。Progress<T>或者Progress<string>在这种情况下特别应该工作得很好)。请参阅@Stephen Cleary 撰写的这篇文章

His example is very close to what you stated in the question. I've copied it here for SO independence.

他的例子非常接近你在问题中所说的。我已将其复制到此处以保持独立性。

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

Edit:I must admit, while Progress<T>is a nice abstraction, in this case it is just going to fall down to Dispatcher.Invoke as @Pamparanpa suggested.

编辑:我必须承认,虽然Progress<T>是一个很好的抽象,但在这种情况下,它只会像@Pamparanpa 建议的那样归结为 Dispatcher.Invoke。