2015-08-26 1 views
20

Ho letto this question da Noseratio che mostra un comportamento in cui TaskScheduler.Current non è lo stesso dopo che una versione attendibile ha terminato l'operazione.attendi non riprende il contesto dopo un'operazione asincrona?

La risposta si afferma che:

Se non c'è un compito reale in corso di esecuzione, quindi TaskScheduler.Current è lo stesso di TaskScheduler.Default

Il che è vero. Ho già visto here:

  • TaskScheduler.Default
    • restituisce un'istanza della ThreadPoolTaskScheduler
  • TaskScheduler.Current
    • Se viene chiamato dall'interno di un compito di eseguire restituirà il TaskScheduler di il compito attualmente in esecuzione
    • Se chiamato da qualsiasi altro posto tornerà TaskScheduler.Default

Ma poi ho pensato, se è così, cerchiamo di fare creare un vero e proprio Task (e non solo Task.Yield()) e testarlo:

async void button1_Click_1(object sender, EventArgs e) 
{ 
    var ts = TaskScheduler.FromCurrentSynchronizationContext(); 
    await Task.Factory.StartNew(async() => 
    { 
     MessageBox.Show((TaskScheduler.Current == ts).ToString()); //True 

      await new WebClient().DownloadStringTaskAsync("http://www.google.com"); 

     MessageBox.Show((TaskScheduler.Current == ts).ToString());//False 

    }, CancellationToken.None, TaskCreationOptions.None,ts).Unwrap(); 
} 

Primo messaggio è "Vero", se cond è "False"

Domanda:

Come potete vedere, ho creato un vero e proprio compito.

Posso capire perché il primo MessageBox produce True. Quello è becuase del:

Se chiamato dall'interno di un compito di eseguire restituirà il TaskScheduler del compito attualmente in esecuzione

E questo compito non hanno ts che è l'inviato TaskScheduler.FromCurrentSynchronizationContext()

Ma perché il contesto è non conservato allo secondo MessageBox? Per me, non era chiaro dalla risposta di Stephan.

Ulteriori informazioni:

Se scrivo invece (del secondo messagebox):

MessageBox.Show((TaskScheduler.Current == TaskScheduler.Default).ToString()); 

lo fa cedere true. Ma perché ?

+0

Ottima domanda, ma cosa succede se "Utilità di pianificazione! = Contesto di sincronizzazione"? – AgentFire

+0

Un buon caso per la regola hard: non assumere mai 'TaskScheduler.Current' è quello che pensi che sia :) È garantito che sia quello che hai passato a 'Factory.StartNew' per l'ambito dell'azione task lambda (che è' Func ' nel tuo caso e restituisce quando colpisce il primo ' await'). Qualsiasi altro comportamento dovrebbe essere trattato come dettagli di implementazione. – Noseratio

risposta

10

Le ragioni di confusione sono questi:

  1. L'interfaccia utente non dispone di una "speciale" TaskScheduler. Il caso predefinito di codice in esecuzione sul thread UI è che TaskScheduler.Current memorizza le ThreadPoolTaskScheduler e SynchronizationContext.Current memorizza WindowsFormsSynchronizationContext (o quella corrispondente in altre applicazioni UI)
  2. ThreadPoolTaskScheduler in TaskScheduler.Current non significa necessariamente è il TaskScheduler utilizzato per eseguire il corrente pezzo di codice. Significa anche TaskSchdeuler.Current == TaskScheduler.Default e quindi "non è utilizzato TaskScheduler".
  3. TaskScheduler.FromCurrentSynchronizationContext() non restituisce un valore "acutale" TaskScheduler. Restituisce un "proxy" che invia i compiti direttamente allo SynchronizationContext catturato.

Quindi, se si esegue il test prima di iniziare l'attività (o in qualsiasi altro luogo) si otterrà lo stesso risultato come dopo l'attendono:

MessageBox.Show(TaskScheduler.Current == TaskScheduler.FromCurrentSynchronizationContext()); // False 

Perché TaskScheduler.Current è ThreadPoolTaskScheduler e TaskScheduler.FromCurrentSynchronizationContext() restituisce un SynchronizationContextTaskScheduler.

Questo è il flusso del vostro esempio:

  • Si crea una nuova SynchronizationContextTaskScheduler dalla UI del SynchronizationContext (vale a dire WindowsFormsSynchronizationContext).
  • Pianificare l'attività creata utilizzando Task.Factory.StartNew su quello TaskScheduler. Poiché è solo un "proxy", invia il delegato allo WindowsFormsSynchronizationContext che lo richiama sul thread dell'interfaccia utente.
  • La parte sincrona (la parte prima della prima attesa) di tale metodo viene eseguita sul thread dell'interfaccia utente, pur essendo associata allo SynchronizationContextTaskScheduler.
  • Il metodo raggiunge l'attesa ed è "sospeso" durante l'acquisizione di quello WindowsFormsSynchronizationContext.
  • Quando la continuazione viene ripresa dopo l'attesa, viene registrata in tale WindowsFormsSynchronizationContext e non nellodal SynchronizationContext s ha la precedenza (this can be seen in Task.SetContinuationForAwait). Quindi, viene eseguito regolarmente sul thread dell'interfaccia utente, senza "speciale" TaskScheduler quindi TaskScheduler.Current == TaskScheduler.Default.

Così, l'attività creata viene eseguito sul proxy TaskScheduler che utilizza il SynchronizationContext ma la continuazione dopo che l'attendono viene registrato che SynchronizationContext e non TaskScheduler.

+0

La continuazione dopo "attendere" viene eseguita su 'ThreadPoolTaskScheduler', non sull'interfaccia utente. –

+0

@YuvalItzchakov Perché dici così? Non sono d'accordo. Viene pubblicato nell'interfaccia utente utilizzando il suo 'SynchronizationContext' – i3arnon

+0

Wait, di cui" stai aspettando "stai parlando? :) –