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)
为什么会这样?(使用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
而不会被读取,因为主线程很快就会覆盖它。