In attesa sincrona di un’operazione asincrona, e perché Wait () blocca il programma qui

Prefazione : sto cercando una spiegazione, non solo una soluzione. Conosco già la soluzione.

Nonostante abbia trascorso diversi giorni a studiare articoli MSDN riguardo al modello asincrono basato su attività (TAP), async e attendi, sono ancora un po ‘confuso su alcuni dei dettagli più fini.

Sto scrivendo un logger per le app di Windows Store e voglio supportare sia la registrazione asincrona che quella sincrona. I metodi asincroni seguono il TAP, quelli sincroni dovrebbero hide tutto questo e guardare e lavorare come metodi ordinari.

Questo è il metodo principale della registrazione asincrona:

private async Task WriteToLogAsync(string text) { StorageFolder folder = ApplicationData.Current.LocalFolder; StorageFile file = await folder.CreateFileAsync("log.log", CreationCollisionOption.OpenIfExists); await FileIO.AppendTextAsync(file, text, Windows.Storage.Streams.UnicodeEncoding.Utf8); } 

Ora il corrispondente metodo sincrono …

Versione 1 :

 private void WriteToLog(string text) { Task task = WriteToLogAsync(text); task.Wait(); } 

Sembra corretto, ma non funziona. L’intero programma si blocca per sempre.

Versione 2 :

Hmm .. Forse il compito non è stato avviato?

 private void WriteToLog(string text) { Task task = WriteToLogAsync(text); task.Start(); task.Wait(); } 

Ciò genera InvalidOperationException: Start may not be called on a promise-style task.

Versione 3:

Hmm .. Task.RunSynchronously sembra promettente.

 private void WriteToLog(string text) { Task task = WriteToLogAsync(text); task.RunSynchronously(); } 

Ciò genera InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.

Versione 4 (la soluzione):

 private void WriteToLog(string text) { var task = Task.Run(async () => { await WriteToLogAsync(text); }); task.Wait(); } 

Questo funziona. Quindi, 2 e 3 sono gli strumenti sbagliati. Ma 1? Cosa c’è di sbagliato in 1 e qual è la differenza in 4? Cosa fa 1 causare un blocco? C’è qualche problema con l’object compito? C’è un punto morto non ovvio?

Per favore aiutami a capire.

L’ await nel tuo metodo asincrono sta tentando di tornare al thread dell’interfaccia utente.

Poiché il thread dell’interfaccia utente è occupato in attesa del completamento dell’intera attività, si verifica un deadlock.

Lo spostamento della chiamata asincrona a Task.Run() risolve il problema.
Poiché la chiamata asincrona è ora in esecuzione su un thread pool di thread, non tenta di tornare al thread dell’interfaccia utente e quindi tutto funziona.

In alternativa, è ansible chiamare StartAsTask().ConfigureAwait(false) prima di attendere l’operazione interna per farlo tornare al pool di thread anziché al thread dell’interfaccia utente, evitando completamente il deadlock.

Chiamare codice async dal codice sincrono può essere piuttosto complicato.

Spiego le ragioni complete di questo deadlock sul mio blog . In breve, c’è un “contesto” che viene salvato di default all’inizio di ogni await e utilizzato per riprendere il metodo.

Quindi, se questo viene chiamato in un contesto di interfaccia utente, al termine await , il metodo async tenta di rientrare in tale contesto per continuare l’esecuzione. Sfortunatamente, il codice che utilizza Wait (o Result ) bloccherà un thread in quel contesto, quindi il metodo async non può essere completato.

Le linee guida per evitare questo sono:

  1. Usa ConfigureAwait(continueOnCapturedContext: false) il più ansible. Ciò consente ai tuoi metodi async di continuare l’esecuzione senza dover reinserire il contesto.
  2. Utilizzare async fino in fondo. Usa Wait invece di Result o Wait .

Se il tuo metodo è naturalmente asincrono, allora (probabilmente) non dovresti esporre un wrapper sincrono .

Ecco cosa ho fatto

 private void myEvent_Handler(object sender, SomeEvent e) { // I dont know how many times this event will fire Task t = new Task(() => { if (something == true) { DoSomething(e); } }); t.RunSynchronously(); } 

funziona bene e non blocca il thread dell’interfaccia utente

Con un piccolo contesto di sincronizzazione personalizzato, la funzione di sincronizzazione può attendere il completamento della funzione asincrona, senza creare deadlock. Ecco un piccolo esempio per l’app WinForms.

 Imports System.Threading Imports System.Runtime.CompilerServices Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load SyncMethod() End Sub ' waiting inside Sync method for finishing async method Public Sub SyncMethod() Dim sc As New SC sc.WaitForTask(AsyncMethod()) sc.Release() End Sub Public Async Function AsyncMethod() As Task(Of Boolean) Await Task.Delay(1000) Return True End Function End Class Public Class SC Inherits SynchronizationContext Dim OldContext As SynchronizationContext Dim ContextThread As Thread Sub New() OldContext = SynchronizationContext.Current ContextThread = Thread.CurrentThread SynchronizationContext.SetSynchronizationContext(Me) End Sub Dim DataAcquired As New Object Dim WorkWaitingCount As Long = 0 Dim ExtProc As SendOrPostCallback Dim ExtProcArg As Object  Public Overrides Sub Post(d As SendOrPostCallback, state As Object) Interlocked.Increment(WorkWaitingCount) Monitor.Enter(DataAcquired) ExtProc = d ExtProcArg = state AwakeThread() Monitor.Wait(DataAcquired) Monitor.Exit(DataAcquired) End Sub Dim ThreadSleep As Long = 0 Private Sub AwakeThread() If Interlocked.Read(ThreadSleep) > 0 Then ContextThread.Resume() End Sub Public Sub WaitForTask(Tsk As Task) Dim aw = Tsk.GetAwaiter If aw.IsCompleted Then Exit Sub While Interlocked.Read(WorkWaitingCount) > 0 Or aw.IsCompleted = False If Interlocked.Read(WorkWaitingCount) = 0 Then Interlocked.Increment(ThreadSleep) ContextThread.Suspend() Interlocked.Decrement(ThreadSleep) Else Interlocked.Decrement(WorkWaitingCount) Monitor.Enter(DataAcquired) Dim Proc = ExtProc Dim ProcArg = ExtProcArg Monitor.Pulse(DataAcquired) Monitor.Exit(DataAcquired) Proc(ProcArg) End If End While End Sub Public Sub Release() SynchronizationContext.SetSynchronizationContext(OldContext) End Sub End Class