8

C'è una variabile Operazione e diciamo che l'attività è in esecuzione in questo momento .. eseguendo la seguente riga.cosa succede se aspetto un'attività già in esecuzione o in esecuzione?

await _task; 

Mi chiedevo che cosa succede quando scrivo questo codice:

await _task; 
await _task; 

sarebbe eseguire l'operazione due volte? O lanciare un'eccezione perché è già stata eseguita?

+4

Perché non ci provi? Non è difficile da testare. – Enigmativity

+0

L'intuizione chiave da avere qui è che * attendere non esegue un'attività *. Await * attende in modo asincrono l'esecuzione di un'attività *. Da qui il nome "attendi". C'è un mito persistente che attende l'inizio di un compito, ma ovviamente non lo fa; hai già un compito iniziato in mano quando lo aspetti. –

+0

Il vero occhio per me è stato il concetto di "continuazioni". Penso che senza questa intuizione, è difficile capire "attendere". – Sentinel

risposta

9

esegue l'attività due volte? O lanciare un'eccezione perché ha già già eseguito?

No e no. L'unica cosa che await fa è chiamare il numero Task.GetAwaiter, non causa l'esecuzione di alcunché. Se l'attività è già stata eseguita fino al completamento, estrarrà il valore se è un Task<T> o eseguirà in modo sincrono la riga successiva se è un Task, poiché esiste un ottimizzazione per le attività già completate.

Un semplice demo:

async Task Main() 
{ 
    var foo = FooAsync(); 
    await foo; 
    await foo; 

    var bar = BarAsync(); 
    var firstResult = await bar; 
    var secondResult = await bar; 

    Console.WriteLine(firstResult); 
    Console.WriteLine(secondResult); 
} 

public async Task FooAsync() 
{ 
    await Task.Delay(1); 
} 

public async Task<int> BarAsync() 
{ 
    await Task.Delay(1); 
    return 1; 
} 

Se si drill-down per la macchina dello Stato in sé, vedrete questo:

this.<foo>5__1 = this.<>4__this.FooAsync(); 
taskAwaiter = this.<foo>5__1.GetAwaiter(); 
if (!taskAwaiter.IsCompleted) 
{ 
    this.<>1__state = 0; 
    this.<>u__1 = taskAwaiter; 
    M.<FooBar>d__0 <FooBar>d__ = this; 
    this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, M.<FooBar>d__0> 
                (ref taskAwaiter, ref <FooBar>d__); 
    return; 
} 

Questa ottimizzazione controlla innanzitutto il completamento del compito. Se l'attività non è completa, chiamerà UnsafeOnCompleted che registrerà la continuazione. Se è completa, si rompe il switch e va a:

this.<>1__state = -2; 
this.<>t__builder.SetResult(); 

che definisce il risultato per il sottostante Task, e in questo modo che effettivamente ottenere il valore in modo sincrono.

+4

E va notato che * questa è una cosa buona *. Una delle parti più complicate del codice asincrono è che la maggior parte delle operazioni asincrone può infatti terminare in modo sincrono - e questo è un caso a parte che devi gestire, perché in questo caso la callback asincrona * non * verrà chiamata. 'await' nasconde da te tutta questa complessità e ti permette di" attendere "lo stesso compito in dieci diversi posti (molto utile per esempio l'inizializzazione pigra asincrona). – Luaan

+0

posso essere sicuro che il metodo indicato dall'attività non verrà eseguito due volte anche se l'attività è già in esecuzione o è già in esecuzione? –

+0

@BilalFazlani Sì, puoi. Se il 'Task' è già completato, sarà semplicemente scartare il risultato. –

6

ho buttato insieme questo test rapido:

var i = 0; 
var task = Task.Run(() => { i++; Console.WriteLine(i); return i; }); 

var x = await task; 
var y = await task; 

Console.WriteLine(x); 
Console.WriteLine(y); 

Scrive:

1 
1 
1 

Quindi, chiaramente, l'attività viene eseguita una sola volta.

+0

Grazie a @Enigmativity. E 'stato davvero d'aiuto –

Problemi correlati