11

async / await当我遇到以下情况时,我正在玩:

class C
{
    private static string str;

    private static async Task<int> FooAsync()
    {
        str += "2";
        await Task.Delay(100);
        str += "4";
        return 5;
    }

    private static void Main(string[] args)
    {
        str = "1";
        var t = FooAsync();
        str += "3";

        str += t.Result; // Line X

        Console.WriteLine(str);
    }
}

我预计结果是“12345”,但结果是“1235”。不知何故,“4”被吃掉了。

如果我将 X 行拆分为:

int i = t.Result;
str += i;

然后是预期的“12345”结果。

为什么会这样?(使用VS2012)

4

3 回答 3

10

为什么会这样?(使用VS2012)

您在控制台应用程序中运行它,这意味着没有当前同步上下文。

因此,在单独的线程中运行FooAsync()之后的方法部分。await当你这样做时str += t.Result,你实际上是在+= 4调用和+= t.Result. 这是因为string +=不是原子操作。

如果您要在 Windows 窗体或 WPF 应用程序中运行相同的代码,则会捕获同步上下文并将其用于+= "4",这意味着这将全部在同一线程上运行,并且您不会看到此问题。

于 2013-05-24T20:49:53.273 回答
7

形式的 C# 语句在编译时x += y;扩展为。x = x + y;

str += t.Result;变成了str = str + t.Result;,在得到之前str读到哪里。在这个时间点,是。当 continuation in运行时,它会修改然后返回. 现在也是。但是随后在run的延续(即)与分配给value之前读取了 that的值。t.Resultstr"123"FooAsyncstr5str"1234"strFooAsync"123"5str"1235"

当你把它分成两个语句时int i = t.Result; str += i;,这种行为就不会发生。

于 2013-05-24T20:52:46.290 回答
6

这是一个竞争条件。您没有正确同步对您拥有的两个执行线程之间的共享变量的访问。

您的代码可能正在执行以下操作:

  • 将字符串设置为“1”
  • 调用 FooAsync
  • 附加2
  • 当调用await时main方法继续执行,FooAsync中的回调会在线程池中运行;从这里开始,事情是不确定的。
  • 主线程将 3 附加到字符串

然后我们进入有趣的行:

str += t.Result;

在这里,它被分解为几个较小的操作。它将首先获取 的当前值str。此时异步方法(很可能)尚未完成,所以它将是"123". 然后它等待任务完成(因为Result强制阻塞等待)并将任务的结果添加5到字符串的末尾。

在主线程已经获取 的当前值str 之后,异步回调将被抓取并重新设置str,然后它将被覆盖str而不会被读取,因为主线程很快就会覆盖它。

于 2013-05-24T20:53:36.897 回答