【问题标题】:Using async await still freezes GUI使用 async await 仍然会冻结 GUI
【发布时间】:2016-06-13 22:28:50
【问题描述】:

我想在单独的线程中处理长时间运行的操作,并使用 async/await 模式尽快将控制权返回给 GUI 线程,如下所示:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await Test();
    txtResult.Text = "Done!";
}

private Task Test()
{
    Thread.Sleep(3000);
    return Task.FromResult(0);
}

问题是,它无论如何都会冻结 GUI 3 秒(它变得无响应,直到完成!在 3 秒后显示)。我做错了什么?

编辑: 我正在尝试替换以下逻辑:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var thread = new Thread(() => Test(Callback));
    thread.Start();
}

private void Callback()
{
    Dispatcher.Invoke(() =>
        txtResult.Text = "Done!");
}

private void Test(Action callback)
{
    Thread.Sleep(3000); //long running operation, not necessarily pause
    callback();
}

在实际项目中,我有不同的长时间运行逻辑,而不仅仅是睡眠,它仍然冻结 GUI,所以用 Task.Delay 替换它并不能解决任何问题。此外,我不明白你为什么要使用另一个睡眠命令? async/await 设计是如何要求的?

【问题讨论】:

  • 异步不会导致您的代码在单独的线程上运行。它允许当前线程在用于创建要在另一个线程上运行的任务或执行要等待的 IO 绑定任务的代码上时不被阻塞。但是,您的方法不会做这些事情。

标签: c# user-interface asynchronous async-await


【解决方案1】:

您正在使用 Thread.Sleep 阻塞线程。 请改用Task.Delay,它在内部使用计时器并异步将控制权交还给调用者:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await TestAsync();
    txtResult.Text = "Done!";
}

private async Task<int> TestAsync()
{
    await Task.Delay(3000);
    return 0;
}

编辑

当您发布新代码时,我建议您隔离需要在日志运行过程之后运行的回调,并为自己保存Dispatcher.Invoke 并使用async-await 替换它,这将使您获得正确的同步你的上下文:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(Test);
    CallBack();
}

private void Callback()
{
    txtResult.Text = "Done!"
}

private void Test()
{
    Thread.Sleep(3000); //long running operation, not necessarily pause
}

【讨论】:

  • 感谢回复,在实际项目中我有长时间运行的逻辑而不是睡眠,请参阅编辑后的问题。
  • 好的,所以 await Task.Run(Test) 异步运行,而 await Task 同步运行?与直接调用 Task() 相比, await Task() 的意义何在?
  • @EugeneKr await 只是一个关键字,用于标记编译器的简单提示。异步不是由await 完成的,而是由正在等待的底层方法完成的。这意味着,例如,如果您使用 HttpClient 进行异步 IO,它自然地公开异步方法,那么当您 await 其中一个方法时,控制权将返回给调用方法。当您使用 Task.Run 时,您在线程池线程上显式运行委托,这与使用自然异步 API 不同,后者不需要额外的线程。
  • 我开始明白这一点......我猜。仍然看不到它在哪里有用。虽然如果我面前有一段使用 await Test() 的示例代码会容易得多,如果可能的话,类似于我提供的那些。希望您仍然有一点耐心来执行此操作 :) 我几乎看不出带有 await Task.Delay 的示例有什么用处。
  • @EugeneKr 我不太清楚你的意思。 await Test(),根据您的示例,没有异步执行任何操作。它有一个阻塞部分,使用Thread.Sleep 模拟,并使用实用程序Task.FromResult,它再次不做任何异步操作,它只是使用TaskCompletionSource&lt;T&gt; 创建一个完整的Task 对象。等待它只会阻塞调用线程。
【解决方案2】:

您可以使用Task.RunTask.Factory.StartNew 在另一个线程上执行Test() 或一些长时间运行和/或阻塞操作:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() => Test());
    txtResult.Text = "Done!";
}

【讨论】:

  • 对不起,为什么应该等待 Task.Run(() => Test());而不仅仅是等待 Test() ?
  • Test() 返回一个同步任务,只是等待它不会在单独的线程上启动任务。 documentation 比我更好地解释了这一点:“async 和 await 关键字不会导致创建额外的线程。”和“您可以使用 Task.Run 将 CPU 绑定的工作移动到后台线程”。
  • Task.Run 是任务并行库的一部分。在这种情况下,它与 await 配合得很好。在 C# 中有很多方法可以在后台做一些事情。我建议阅读此快速演练以更好地理解 markheath.net/post/starting-threads-in-dotnet
  • 这个答案对我有帮助,谢谢你的提问和回答:-)
猜你喜欢
  • 1970-01-01
  • 2016-02-13
  • 1970-01-01
  • 2014-09-25
  • 2015-09-30
  • 2019-04-22
  • 2020-10-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多