任务<t> .Result和字符串连接</t>

时间:2013-05-24 20:44:20

标签: c# .net c#-5.0

当我遇到以下情况时,我正在玩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)

3 个答案:

答案 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}}中继续运行str之前读取FooAsync的值("123")与5连接以分配给str "1235"int i = t.Result; str += i;

当你将它分成两个语句{{1}}时,就不会发生这种情况。

答案 2 :(得分:6)

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

你的代码可能是这样的:

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

然后我们进入有趣的界限:

str += t.Result;

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

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