当我遇到以下情况时,我正在玩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)
答案 0 :(得分:10)
为什么会这样? (使用VS2012)
您在控制台应用程序中运行它,这意味着没有当前的同步上下文。
因此,FooAsync()
之后await
方法的一部分在一个单独的线程中运行。当您执行str += t.Result
时,您有效地在+= 4
来电和+= t.Result
之间形成竞争条件。这是因为string +=
不是原子操作。
如果要在Windows窗体或WPF应用程序中运行相同的代码,则会捕获同步上下文并将其用于+= "4"
,这意味着这将在同一个线程上运行,并且您不会看到这个问题。
答案 1 :(得分:7)
x += y;
形式的C#语句在编译时扩展为x = x + y;
。
str += t.Result;
变为str = str + t.Result;
,其中str
在获取t.Result
之前读取。目前,str
为"123"
。当FooAsync
中的续集运行时,它会修改str
,然后返回5
。所以str
现在是"1234"
。但是,在<{1}}中继续1>运行str
之前读取的FooAsync
的值("123"
)与5
连接以分配给str
"1235"
值int i = t.Result; str += i;
。
当你将它分成两个语句{{1}}时,就不会发生这种情况。
答案 2 :(得分:6)
这是竞争条件。您没有正确地同步对两个执行线程之间的共享变量的访问。
你的代码可能是这样的:
然后我们进入有趣的界限:
str += t.Result;
这里分为几个较小的操作。它将首先获得str
的当前值。此时异步方法(很可能)尚未完成,因此它将是"123"
。然后它等待任务完成(因为Result
强制阻塞等待)并将任务结果(在本例中为5
)添加到字符串的末尾。
在主线程已经抓取当前值str
之后,异步回调将抓取并重新设置str
,然后它将覆盖{{1没有它被读取,因为主线程很快就会覆盖它。