【问题标题】:Are you there, asynchronously written value?你在吗,异步写入的值?
【发布时间】:2015-05-06 11:16:26
【问题描述】:

最近几天我一直在阅读有关 async/await 的内容。昨天我在第 9 频道找到了this video,这让我对某些事情感到好奇。请考虑下面的幻灯片。

除了 Lucian Wischik 解决的问题之外,我还想知道变量赋值。假设我们将async void 更改为async Task,并在SendData 调用之前添加了await。这使我们能够获取流,分配变量m_GetResponse,等待两秒钟并打印它。但是变量会发生什么?它可以由与读取不同的线程编写。我们是否需要某种内存屏障,使变量易失,或者其他什么?当我们打印它时它仍然是空的吗?

【问题讨论】:

  • 我发现这两个答案都一样好,但我不能同时接受这两个答案。对此感到抱歉。 =)
  • async-await 对并发没有任何作用。这本身就是另一个问题。

标签: c# asynchronous thread-safety async-await


【解决方案1】:

但是变量会发生什么?它可以由不同的人编写 线程比它被读取。

如果m_GetResponse 是一个私有字段,并且这个类被不同的线程多次调用,那么是的,一旦其他人试图读取它,该值可能是“脏”的。为了使其线程安全,您可以在它周围使用lock。似乎作者的意图是仅从 UI 线程调用它,因此他将 SendData 设为私有方法。在这种情况下,m_GetResponse 是一个私有字段是安全的,因为负责变量分配的异步方法的延续将发生在 UI 消息循环内。

打印出来的时候还是null吗?

如果代码中的其他地方有人将该变量设置为null,它可能是null,因为它是一个类级别的变量。如果您在谈论“可能是我们尝试在await 完成状态机执行之前打印m_GetResponse,那么不会。同样,我不确定作者的意图是围绕并发执行,而是向您展示async-await 功能。

或者别的什么?

为了使其线程安全,您可以简单地删除全局变量,并返回一个局部变量。 SendData 无论如何都不应该是 async void,因为它不用于像 Button1_Click 这样的事件处理程序委托分配。

你可以这样做得更好(为简单起见,我将使用HttpClient):

public async Task<string> SendDataAsync(string url)
{
    var httpClient = new HttpClient();
    var response = await httpClient.GetAsync();
    return response.Content.ReadAsStringAsync();
}

请注意,您应该记住,async-await 并不是为了解决并行性,它更多的是关于并发和简化自然异步 IO 操作的使用。

【讨论】:

    【解决方案2】:

    在上面的示例中,读取m_GetResponse 是安全的,因为分配将发生在同一个 UI 线程中,因为这是从 UI 调用的。

    这是因为SynchronizationContext 将在异步方法恢复时被捕获并继续。因此,写入和读取字段是同一个 UI 线程。这不是问题。请参考我的related answer here

    如果从非 UI 上下文中调用,则不能保证延续将在同一个线程中运行。通常它会在 ThreadPool 线程中运行。鉴于读取的字段不是易失性的,如果未插入必要的障碍,您可能会获得先前的值。不过你不用担心因为TPL already does this for you

    从上面的链接

    是的,当任务排队时,TPL 包括适当的障碍,并且 在任务执行的开始/结束时,以便值是 适当地可见

    因此,使用 TPL,您无需担心内存障碍,因为任务已经完成。但是如果你手动创建线程(你不应该这样做)并直接处理线程——你将不得不插入必要的内存屏障。

    顺便说一句,ReadToEnd 是一个阻塞调用。我不会在 UI 线程中调用它。我会使用ReadToEndAsync 来使您的 UI 线程免费。我不会在这里使用字段;我将从异步方法返回值,因为每个方法调用都只依赖于参数,因此从方法返回值是有意义的。

    所以,你的方法会变成下面这样

    private async Task<string> SendDataAsync(string url)
    {
        var request = WebRequest.Create(url);
        using(var response = await request.GetResponseAsync());
        using(var reader = new StreamReader(request.GetResponseStream());
            return await reader.ReadToEndAsync();
    }
    

    【讨论】:

    • async/await 确实提供了完整的内存屏障,因此即使在非 UI 上下文中,如果您使用 await 也不会有看不到更新值的危险。这只是一个真正的竞争条件场景中的一个问题,其中多个线程可以更新值。
    • @StephenCleary 感谢您提供信息。我的快速谷歌搜索发现stephen toub's comment here。我已经更新了我的答案,请检查。
    • TPL 博客现已存档here,没有原始 cmets。 The WayBack machine has it covered,不过。
    • @tm1 感谢您提及链接已损坏。将更新新链接。为什么 msft 不能提供对旧链接的重定向:(
    猜你喜欢
    • 2018-04-19
    • 2021-12-29
    • 1970-01-01
    • 2023-04-06
    • 1970-01-01
    • 2020-04-03
    • 2017-06-07
    • 1970-01-01
    相关资源
    最近更新 更多