[英]How does C# Task.WaitAll() combine object states into one?
以一個簡單的酒店實體為例:
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()的調用都成功通過。 我不明白在兩個任務完成后,Hotel的實例包含兩個並行運行的方法的賦值。
我認為當調用await
時(在SetRooms()
和SetStars()
),會創建一個酒店實例的“快照”(將NumberOfRooms
和StarRating
設置為0)。 所以我的期望是兩個任務之間會有競爭條件,最后一個將被復制回hotel
,在兩個屬性中的一個產生0。
顯然我錯了。 你能解釋我在哪里誤解了等待是如何工作的嗎?
我認為當調用await時(在SetRooms()和SetStars()中),會創建一個酒店實例的“快照”
您的Hotel
類是參考類型。 當您使用async-await時,您的方法將轉換為狀態機,並且該狀態機將對您的變量的引用提升到它上面。 這意味着創建的兩個狀態機都指向同一個Hotel
實例 。 您的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;
}
您可以看到兩種方法都將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));
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.