await - thread c# task




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

Avec un petit contexte de synchronisation personnalisé, la fonction de synchronisation peut attendre l'achèvement de la fonction asynchrone, sans créer de blocage. Voici un petit exemple pour l'application 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

    <MethodImpl(MethodImplOptions.Synchronized)>
    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

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

En dépit d'avoir passé plusieurs jours à étudier les articles MSDN sur le TAP (Asynchronous Pattern) basé sur les tâches, async et await, je suis encore un peu confus à propos de certains détails plus fins.

J'écris un enregistreur pour Windows Store Apps, et je veux prendre en charge la journalisation asynchrone et synchrone. Les méthodes asynchrones suivent le TAP, les synchrones doivent cacher tout cela, et ressemblent et fonctionnent comme des méthodes ordinaires.

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

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

Maintenant, la méthode synchrone correspondante ...

Version 1 :

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

Cela semble correct, mais cela ne fonctionne pas. L'ensemble du programme se bloque pour toujours.

Version 2 :

Hmm .. Peut-être que la tâche n'a pas commencé?

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

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

Version 3:

Hmm .. Task.RunSynchronously semble prometteur.

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

Cela 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(string 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 cause un gel? Y a-t-il un problème avec l'objet de tâche? Y a-t-il une impasse non évidente?

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


L' await intérieur de votre méthode asynchrone tente de revenir au thread de l'interface utilisateur.

Comme le thread de l'interface utilisateur est occupé à attendre que l'intégralité de la tâche soit terminée, vous avez un blocage.

Déplacer l'appel asynchrone à Task.Run() résout le problème.
Parce que l'appel asynchrone est maintenant en cours d'exécution sur un thread de pool de threads, il n'essaie pas de revenir au thread de l'interface utilisateur, et tout fonctionne donc.

Vous pouvez également appeler StartAsTask().ConfigureAwait(false) avant d'attendre l'opération interne pour le faire revenir au pool de threads plutôt qu'au thread d'interface utilisateur, évitant ainsi le blocage complet.

Voir http://ermau.com/avoiding-callbacks-to-the-ui-thread-with-async-and-winrt/


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 très bien et ne bloque pas le fil de l'interface utilisateur





async-await