你在那里,异步写入值吗?

Are you there, asynchronously written value?

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

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

But what happens to the variable? It could be written by a different thread than it is read.

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

Could it still be null when we print it?

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

or perhaps something else?

为了线程安全,您可以简单地删除全局变量,return一个局部变量。 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 操作。

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

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

如果从非 UI 上下文中调用,则没有 gua运行tee 表明延续将 运行 在同一线程中。在 ThreadPool 线程中通常为 运行。鉴于读取的字段不是易变的,如果未插入必要的障碍,您可能会获得以前的值。但你不必担心,因为 TPL already does this for you.

从上面link

Yes, TPL includes the appropriate barriers when tasks are queued and at the beginning/end of task execution so that values are appropriately made visible

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

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

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

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