2015-12-14 8 views
6

Biorąc pod uwagę prostą podmiot Hotel jako przykład:W jaki sposób C# Task.WaitAll() łączy stany obiektów w jeden?

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

Proszę rozważyć następujący kod w 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; 
} 

oba połączenia do Debug.Assert() przechodzą pomyślnie. Nie rozumiem, jak po wykonaniu obu zadań instancja Hotelu zawiera przypisanie obu metod, które działają równolegle.

Myślałem, że gdy await nazywa się (w obu SetRooms() i SetStars()), „migawkę” instancji hotelowy jest tworzony (posiadające zarówno NumberOfRooms i StarRating ustawiony na 0). Tak więc oczekiwałem, że pomiędzy dwoma zadaniami wystąpi warunek wyścigu, a ostatni, który zostanie uruchomiony, zostanie skopiowany z powrotem do hotel, uzyskując 0 w jednej z dwóch właściwości.

Oczywiście, jestem w błędzie. Czy możesz wyjaśnić, gdzie nie rozumiem, jak działa oczekiwanie?

+1

Skąd pomysł o „migawki” z? :) –

+0

Z opisu maszyny stanu utworzonej przez kompilator chyba ...? :( – urig

Odpowiedz

16

Myślałem, że gdy Oczekujcie nazywa się (w obu SetRooms() i SetStars()), "migawkę" na przykład hotel jest stworzony

Twoja klasa Hotel to rodzaj odniesienia. Kiedy używasz async-await, twoja metoda jest przekształcana w stan-maszynę, i ta maszyna-maszyna podciąga referencję do twojej zmiennej na to. Oznacza to, że oba utworzone maszyny stanów wskazują na taką samą instancję Hotel. Nie ma "migawki" ani głębokiej kopii twojego Hotel, kompilator tego nie robi.

Jeśli chcesz zobaczyć, co faktycznie dzieje się, you can have a look at what the compiler emits raz przekształca swoje metody async:

[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; 
} 

Widać, że obie metody podnosić zmienną hotel do ich stanu maszyny.

Więc moje oczekiwanie, że nie będzie wyścigu między dwa zadania i ostatniego uruchomienia będzie jeden kopiowane z powrotem do hotelu uzyskując 0 w jednym z dwóch właściwości.

Teraz, gdy widzisz, co faktycznie robi kompilator, możesz zrozumieć, że naprawdę nie ma warunków wyścigu. To jest to samo wystąpienie Hotel, które jest modyfikowane, każda metoda ustawia inną zmienną.


uwaga Side

Może napisał ten kod tylko jako przykład, aby wyjaśnić swoje pytanie, ale jeśli jesteś już metody tworzenia async, polecam korzystania Task.WhenAll zamiast blokowania Task.WaitAll .Oznacza to zmianę podpis Run do async Task zamiast 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)); 
} 
+0

Dzięki @ yuval-itzchakov.Jakie są zalety używania 'Task.WhenAll()' nad 'Task.WaitAll()'? – urig

+1

'Task.WhenAll' nie blokuje, a więc asynchronicznie uzyskując kontrolę do metody wywołania , w przeciwieństwie do 'Task.WaitAll', który synchronicznie blokuje. Również obsługa wyjątków jest trochę inna. Patrz [this] (http://stackoverflow.com/questions/25009437/running-multiple-async-tasks-and-waiting -for-all-to-complete/25010220 # 25010220) pytanie na więcej –

+0

Widząc jak będę natychmiast wywoływać 'Czekaj()' na zadaniu' zwrócony z 'Task.WhenAll()', ma przewagę wciąż trzymaj? – urig

Powiązane problemy