C#Task.WaitAll()如何将对象状态合并为一个?

时间:2015-12-14 10:30:30

标签: c# asynchronous async-await task-parallel-library task

以一个简单的酒店实体为例:

class Hotel
{
    public int NumberOfRooms { get; set; }
    public int StarRating { get; set; }
}

请在C#5.0中考虑以下代码:

public void Run()
{
    var hotel = new Hotel();
    var tasks = new List<Task> { SetRooms(hotel), SetStars(hotel) };
    Task.WaitAll(tasks.ToArray());
    Debug.Assert(hotel.NumberOfRooms.Equals(200));
    Debug.Assert(hotel.StarRating.Equals(5));
}

public async Task SetRooms(Hotel hotel)
{
    await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
    hotel.NumberOfRooms = 200;
}

public async Task SetStars(Hotel hotel)
{
    await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
    hotel.StarRating = 5;
}

对Debug.Assert()的两次调用都成功通过。我不明白在两个任务完成后,酒店的实例包含两个并行运行的方法的分配。

我认为在调用await时(在SetRooms()SetStars()中),会创建酒店实例的“快照”(同时包含NumberOfRooms和{ {1}}设置为0)。所以我的期望是两个任务之间会有一个竞争条件,最后一个任务将被复制回StarRating,在两个属性之一中产生一个0。

显然我错了。你能解释我在哪里误解了等待是如何工作的吗?

1 个答案:

答案 0 :(得分:16)

  

我认为当调用await时(在SetRooms()和。)中   SetStars()),创建了酒店实例的“快照”

您的Hotel班级是参考类型。当您使用async-await时,您的方法将转换为状态机,并且该状态机将引用提升到您的变量上。这意味着创建的两个状态机都指向相同的Hotel实例。 <{1}}没有“快照”或深层副本,编译器不会这样做。

如果你想看看实际发生了什么,you can have a look at what the compiler emits一旦它转换你的异步方法:

Hotel

您可以看到两种方法都将[AsyncStateMachine(typeof(C.<SetRooms>d__1))] public Task SetRooms(Hotel hotel) { C.<SetRooms>d__1 <SetRooms>d__; <SetRooms>d__.hotel = hotel; <SetRooms>d__.<>t__builder = AsyncTaskMethodBuilder.Create(); <SetRooms>d__.<>1__state = -1; AsyncTaskMethodBuilder <>t__builder = <SetRooms>d__.<>t__builder; <>t__builder.Start<C.<SetRooms>d__1>(ref <SetRooms>d__); return <SetRooms>d__.<>t__builder.Task; } [AsyncStateMachine(typeof(C.<SetStars>d__2))] public Task SetStars(Hotel hotel) { C.<SetStars>d__2 <SetStars>d__; <SetStars>d__.hotel = hotel; <SetStars>d__.<>t__builder = AsyncTaskMethodBuilder.Create(); <SetStars>d__.<>1__state = -1; AsyncTaskMethodBuilder <>t__builder = <SetStars>d__.<>t__builder; <>t__builder.Start<C.<SetStars>d__2>(ref <SetStars>d__); return <SetStars>d__.<>t__builder.Task; } 变量提升到状态机中。

  

所以我的期望是两者之间会有竞争条件   两个任务和最后一个任务将被复制回酒店   在两个属性中的一个中产生0。

既然您已经看到编译器实际执行的操作,您就可以理解,确实没有竞争条件。它是被修改的hotel的相同实例,每个方法设置不同的变量。

旁注

也许您编写此代码只是为了解释您的问题,但如果您已经在创建异步方法,我建议您使用Hotel而不是阻止Task.WhenAll。这意味着将Task.WaitAll的签名更改为Run而不是async Task

void