C# 任务<;T>;。结果和字符串连接

C# 任务<;T>;。结果和字符串连接,c#,.net,c#-5.0,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(st

我在玩
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”结果

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

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

您正在控制台应用程序中运行此操作,这意味着没有当前的同步上下文

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

如果要在Windows窗体或WPF应用程序中运行相同的代码,则同步上下文将被捕获并用于
+=“4”
,这意味着这将在同一线程上运行,并且您不会看到此问题扩展为
x=x+y在编译时

str+=t.结果变为
str=str+t.结果
,其中在获取
t.Result
之前读取
str
。此时,
str
“123”
。当
fooanc
中的continuation运行时,它修改
str
,然后返回
5
。所以
str
现在是
“1234”
。但是,在
fooancy
ran(即
“123”
)中的continuation之前读取的
str
值与
5
相连,以分配给
str
“1235”


当你把它分成两条语句时,
inti=t.Result;str+=i,此行为不可能发生。

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

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

  • 将字符串设置为“1”
  • 调用异步
  • 附加2
  • 当调用wait时,main方法继续执行,foosync中的回调将在线程池中运行;从现在开始,事情是不确定的
  • 主线程在字符串后面追加3
然后我们进入有趣的一行:

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


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

如果
return 5
发生在
str+=4
之后的
fooancy
?@IlyaKogan,因为
+=/code>不是一个原子操作,怎么会有竞态条件呢。它被分解成几个子操作,这些子操作可以(在本例中是)相互交织以生成未定义的结果。@Ilykogan竞态条件之所以出现,是因为
str+=t.Result
实际上是
str=str+t.Result
,而“str”是在
+=4
完成之前获取的,但还应该注意的是,您不应该这样做-使用非原子操作(无锁或其他线程逻辑)共享对变量的访问肯定会使您陷入不确定的情况。当分解为两个语句时,这种行为仍然可能发生。只是可能性不大。见里德的答案。
str += t.Result;