Attente synchrone d’une opération asynchrone et pourquoi Wait () gèle le programme ici

Préface : Je cherche une explication, pas seulement une solution. Je connais déjà la solution.

Malgré avoir passé plusieurs jours à étudier les articles MSDN sur le TAP (Asynchronous Pattern) basé sur les tâches, asynchrone et attendre, je suis encore un peu confus au sujet de certains détails plus fins.

J’écris un enregistreur pour les applications Windows Store et je souhaite prendre en charge la journalisation asynchrone et synchrone. Les méthodes asynchrones suivent le TAP, les méthodes synchrones devraient cacher tout cela et ressembler à des méthodes ordinaires.

C’est la méthode de base de la journalisation asynchrone:

private async Task WriteToLogAsync(ssortingng 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); } 

Maintenant la méthode synchrone correspondante …

Version 1 :

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

Cela semble correct, mais cela ne fonctionne pas. Le programme entier se fige pour toujours.

Version 2 :

Hmm .. Peut-être que la tâche n’a pas été lancée?

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

Cela InvalidOperationException: Start may not be called on a promise-style task.

Version 3:

Hmm .. Task.RunSynchronously semble prometteur.

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

Cela génère InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.

Version 4 (la solution):

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

Cela marche. Donc, 2 et 3 sont les mauvais outils. Mais 1? Quel est le problème avec 1 et quelle est la différence à 4? Qu’est-ce qui fait que je provoque un gel? Y a-t-il un problème avec l’object tâche? Y a-t-il une impasse non évidente?

S’il vous plaît, aidez-moi à comprendre.

L’ await dans votre méthode asynchrone tente de revenir au thread d’interface utilisateur.

Étant donné que le thread d’interface utilisateur attend que la tâche soit terminée, vous êtes bloqué.

Déplacer l’appel asynchrone à Task.Run() résout le problème.
Étant donné que l’appel asynchrone s’exécute maintenant sur un thread de pool de threads, il n’essaie pas de revenir au thread d’interface utilisateur et, par conséquent, tout fonctionne.

Vous pouvez également appeler StartAsTask().ConfigureAwait(false) avant d’attendre l’opération interne pour la faire revenir au pool de threads plutôt qu’au thread UI, en évitant totalement le blocage.

Appeler un code async partir d’un code synchrone peut être assez compliqué.

J’explique toutes les raisons de cette impasse sur mon blog . En bref, il y a un “contexte” qui est enregistré par défaut au début de chaque await et utilisé pour reprendre la méthode.

Donc, si elle est appelée dans un contexte d’interface utilisateur, lorsque l’ await terminée, la méthode async tente de ressaisir ce contexte pour continuer à s’exécuter. Malheureusement, le code utilisant Wait (ou Result ) bloquera un thread dans ce contexte, de sorte que la méthode async ne peut pas se terminer.

Les lignes direcsortingces pour éviter cela sont les suivantes:

  1. Utilisez ConfigureAwait(continueOnCapturedContext: false) autant que possible. Cela permet à vos méthodes async de continuer à s’exécuter sans avoir à ressaisir le contexte.
  2. Utilisez async tout le chemin. Utilisez Wait au lieu de Result ou Wait .

Si votre méthode est naturellement asynchrone, alors vous (probablement) ne devriez pas exposer un wrapper synchrone .

Voici ce que j’ai fait

 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(); } 

fonctionne bien et ne bloque pas le thread de l’interface utilisateur

Avec un petit contexte de synchronisation personnalisé, la fonction de synchronisation peut attendre la fin de la fonction asynchrone sans créer de blocage. Voici un petit exemple pour l’application WinForms.

 Imports System.Threading Imports System.Runtime.ComstackrServices 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