Task< T>.结果和字符串连接

本文关键字:字符串 连接 结果 Task | 更新日期: 2023-09-27 18:15:09

我在玩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)

Task< T>.结果和字符串连接

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

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

因此,await之后的FooAsync()方法部分在一个单独的线程中运行。当您执行str += t.Result时,您实际上是在+= 4调用和+= t.Result调用之间创建了一个竞争条件。这是因为string +=不是原子操作。

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

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

str += t.Result;变成str = str + t.Result;,其中str在得到t.Result之前被读取。此时,str"123"。当FooAsync中的continuation运行时,它修改str,然后返回5。所以str变成了"1234"。然后,在FooAsync(即"123")的延续中,在之前读取的str的值与5连接,将值"1235"赋给str

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

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

你的代码可能是这样做的:

  • 将字符串设置为"1"
  • 调用FooAsync
  • 附加2
  • 当调用await时,main方法继续执行,FooAsync中的回调将在线程池中运行;从现在开始,一切都是不确定的。
  • 主线程将3附加到字符串

然后我们进入有趣的行:

str += t.Result;

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

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