你在那里,异步写入值?

Car*_*iel 7 c# asynchronous thread-safety async-await

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

任务中的变量分配

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

Sri*_*vel 4

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

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

如果从非 UI 上下文调用,则不能保证延续将在同一线程中运行。通常它会在ThreadPool线程中运行。鉴于读取的字段不是易失性的,如果未插入必要的屏障,您可能可以获得先前的值。但您无需担心,因为TPL 已经为您完成了此操作Web 存档)。

从上面的链接:

任务库是否在某些点自动包含内存屏障?例如,它会在延续之前和/或之后包含内存屏障吗?

是的,当任务排队时以及任务执行开始/结束时,TPL 包括适当的屏障,以便适当地使值可见。

(Stephen Toub 的回答,2012 年 5 月 1 日)

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

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

所以,你的方法将变成如下所示

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();
}
Run Code Online (Sandbox Code Playgroud)

  • async/await 确实提供了完整的内存屏障,因此即使在非 UI 上下文中,如果您使用 wait,也不会有看不到更新值的危险。这只是真正的竞争条件场景中的一个问题,其中多个线程可以更新该值。 (2认同)