2015-12-14 10 views
6

dato un semplice un'entità hotel come esempio:In che modo C# Task.WaitAll() combina gli stati oggetto in uno?

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

perche il seguente codice in 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; 
} 

le chiamate per Debug.Assert() passa con successo. Non capisco come dopo il completamento di entrambe le attività, l'istanza di Hotel contenga l'assegnazione di entrambi i metodi eseguiti in parallelo.

ho pensato che quando si chiama await (sia SetRooms() e SetStars()), una "istantanea" della istanza hotel viene creato (avendo entrambi impostati NumberOfRooms e StarRating-0). Quindi la mia aspettativa era che ci fosse una condizione di competizione tra i due compiti e l'ultimo a essere eseguito sarà quello copiato a hotel che produce uno 0 in una delle due proprietà.

Ovviamente mi sbaglio. Puoi spiegare dove sto fraintendendo come funziona l'attesa?

+1

Dove hai preso l'idea circa la "istantanea" da? :) –

+0

Dalla descrizione della macchina a stati creata dal compilatore, immagino ...? :( – urig

risposta

16

ho pensato che quando await viene chiamato (in entrambi i SetRooms() e SetStars()), una "istantanea" della istanza hotel viene creata

La vostra classe Hotel è un tipo di riferimento. Quando si utilizza async-await, il metodo viene trasformato in una macchina di stato e tale macchina di stato solleva il riferimento alla variabile su di esso. Ciò significa che entrambe le macchine a stati create puntano a la stessa istanza Hotel. Non esiste alcuna "istantanea" o copia profonda del tuo Hotel, il compilatore non lo fa.

Se volete vedere che cosa effettivamente va avanti, you can have a look at what the compiler emits una volta che trasforma i vostri metodi asincroni:

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

Si può vedere che entrambi i metodi issano la variabile hotel nel loro stato-macchina.

Quindi la mia aspettativa era che ci sarà una condizione di competizione tra i due compiti e l'ultima per l'esecuzione sarà quello copiato tornare in hotel ottenendo un 0 in una delle due proprietà.

Ora che si vede ciò che effettivamente fa il compilatore, si può capire che non c'è davvero una condizione di competizione. È la stessa istanza di Hotel che viene modificata, ogni metodo che imposta la variabile diversa.


Nota a margine

Forse ha scritto questo codice solo per fare un esempio per spiegare la tua domanda, ma se si sta già creando metodi asincroni, mi consiglia di utilizzare Task.WhenAll al posto del blocco Task.WaitAll .Questo significa cambiare la firma di Run-async Task invece di 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

Grazie @ yuval-itzchakov. Qual è il vantaggio dell'uso di 'Task.WhenAll()' su 'Task.WaitAll()'? – urig

+1

'Task.WhenAll' non è bloccante, quindi offre il controllo in modo asincrono al metodo di chiamata , al contrario di 'Task.WaitAll' che blocca in modo sincrono.Inoltre, la gestione delle eccezioni è leggermente diversa.Vedi [this] (http://stackoverflow.com/questions/25009437/running-multiple-async-tasks-and-waiting -for-them-all-to-complete/25010220 # 25010220) per ulteriori domande –

+0

Visto che chiamerò immediatamente 'Wait()' sul Task' restituito da 'Task.WhenAll()', il vantaggio è ancora tieni premuto? – urig