【问题标题】:When will an async await method return from the UI-Thread? At the first await or at the inner await?什么时候会从 UI 线程返回异步等待方法?在第一次等待还是在内部等待?
【发布时间】:2020-05-21 04:21:27
【问题描述】:

我在 UI 线程上的 WPF 中使用异步等待。我有一个关于 async await 在特定情况下如何工作的问题。我将尝试通过一个例子来解释它。

背景

假设我有带有单击事件处理程序ButtonA_Click() 的按钮A 和带有ButtonB_Click() 的按钮B。点击处理程序将检查RunLoop() 方法是否仍在运行。如果它没有运行,它们会启动RunLoop()

问题:

  1. 当(外部)await RunLoop() 被调用时,事件处理程序是否会从 UI 线程返回?
  2. 或者它会在RunLoop() 的(内部)等待处返回,因为它可以在Runloop() 的开头执行同步代码(_isStopped = false;)。

如果 1. 为真,它可能会导致赛车状况,或者?当同时调用两个点击处理程序时,它们将在 UI-Thread 上紧接着运行。因此,第二个事件处理程序可能会在第一次执行 RunLoop() 之前运行。在这种情况下,_isStopped 对于第二个事件处理程序仍然是错误的,它也会启动一个RunLoop()。因此 2 RunLoop() 将处于活动状态。对吗?

(PS:我知道,如果 1. 是这种情况,我可以通过将_isStopped = false 放入点击处理程序来解决这个问题,但这个问题更多的是关于了解当方法嵌套时异步等待如何工作) .

代码

private bool _isStopped = true;
private List<Customer> _customers;  // will be filled from somewhere else

public async void ButtonA_Click()
{
    // do some stuff synchronously

    if (_isStopped)
        await RunLoop();
}

public async void ButtonB_Click()
{
    // do some stuff synchronously

    if (_isStopped)
        await RunLoop();
}

private async Task RunLoop()
{
    _isStopped = false;

    while (_customers.Any())
    {
        _customers.Remove(...);
        await Task.Run(() => ProcessStuff());
    }        

    _isStopped = true;
}

得到答案后更新:

这些链接帮助我更好地理解它

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/control-flow-in-async-programs

【问题讨论】:

  • 你知道List&lt;Customer&gt; _customers不是线程安全的,它是由多个线程并发访问而不同步的吗?
  • @TheodorZoulias 好点。它可以被2个线程访问。这是我遇到的问题的简化版本。我发现我在创建这个版本时犯了一个错误。客户实际上在 Task.Run() 上方的 while 循环中被删除,而不是在 ProcessStuff() 中。但这并没有改变真正的问题。
  • 我打算这么建议。只要您仅从 UI 线程访问和操作 List,就不会出现任何线程安全问题。 :-)

标签: c# wpf async-await


【解决方案1】:

await 被赋予一个不完整 Task 时,控制返回到调用方法。

这意味着首先,await 必须被赋予一个Task,这意味着您调用的方法必须在await 执行任何操作之前返回。其次,Task 必须不完整。如果await 看到完整的Task,则同步恢复执行。

在你的情况下,假设ButtonA_Click() 运行:

  1. ButtonA_Click() 开始同步运行。
  2. 执行RunLoop(),开始同步运行。
  3. 执行Task.Run,会返回一个不完整的Task
  4. RunLoop() 中的await 看到不完整的Task 并返回自己的不完整Task
  5. ButtonA_Click() 中的await 看到不完整的Task,通常会返回Task,但方法是void,所以它什么也不返回。
  6. 控制权被传递给ButtonA_Click()

Microsoft 在Asynchronous programming with async and await 上有一些写得很好的文章,值得一读。

【讨论】:

  • 谢谢。实际上,我一周前阅读了这篇文章和许多其他文章。这是一篇很好的文章,但这个案例在阅读后对我来说并不清楚。但我刚刚发现......它在左侧导航中有多个页面。我现在要检查其余的。我认为这个正在处理这个话题:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
【解决方案2】:

这将是第二个选择。 async 方法中的代码同步运行,直到遇到等待未完成的 Taskawait 语句。

请注意,如果所有等待的Tasks 在等待之前完成,或者如果没有代码实际上需要await 语句,它可能会一直同步运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-13
    • 2021-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-30
    相关资源
    最近更新 更多