以一个简单的酒店实体为例:
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{ 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()的调用都成功通过.我不明白在两个任务完成后,Hotel的实例包含两个并行运行的方法的赋值.
我认为,当await
被调用(在这两个SetRooms()
和SetStars()
),"快照"酒店实例的创建(同时具有NumberOfRooms
与StarRating
设置为0).所以我的期望是两个任务之间会有一个竞争条件,最后一个任务将被复制回hotel
两个属性中的一个产生0.
显然我错了.你能解释我在哪里误解了等待是如何工作的吗?
我认为当调用await时(在SetRooms()和SetStars()中),会创建一个酒店实例的"快照"
您的Hotel
类是引用类型.当您使用async-await时,您的方法将转换为状态机,并且该状态机将对您的变量的引用提升到它上面.这意味着创建的两个状态机都指向同一个Hotel
实例.没有"快照"或深层副本Hotel
,编译器不这样做.
如果要查看实际情况,可以查看编译器在转换异步方法后发出的内容:
[AsyncStateMachine(typeof(C.d__1))] public Task SetRooms(Hotel hotel) { C. d__1 d__; d__.hotel = hotel; d__.<>t__builder = AsyncTaskMethodBuilder.Create(); d__.<>1__state = -1; AsyncTaskMethodBuilder <>t__builder = d__.<>t__builder; <>t__builder.Start d__1>(ref d__); return d__.<>t__builder.Task; } [AsyncStateMachine(typeof(C. d__2))] public Task SetStars(Hotel hotel) { C. d__2 d__; d__.hotel = hotel; d__.<>t__builder = AsyncTaskMethodBuilder.Create(); d__.<>1__state = -1; AsyncTaskMethodBuilder <>t__builder = d__.<>t__builder; <>t__builder.Start d__2>(ref d__); return d__.<>t__builder.Task; }
您可以看到两种方法都将hotel
变量提升到状态机中.
所以我的期望是两个任务之间会有竞争条件,最后一个将被复制回酒店,在两个属性中的一个产生0.
既然您已经看到编译器实际执行的操作,您就可以理解确实没有竞争条件.它Hotel
正在修改相同的实例,每个方法设置不同的变量.
边注
也许您编写此代码只是为了解释您的问题,但如果您已经在创建异步方法,我建议使用Task.WhenAll
而不是阻止Task.WaitAll
.这意味着改变的签名Run
来async Task
代替void
:
public async Task RunAsync() { var hotel = new Hotel(); await Task.WhenAll(SetRooms(hotel), SetStars(hotel)); Debug.Assert(hotel.NumberOfRooms.Equals(200)); Debug.Assert(hotel.StarRating.Equals(5)); }