184 votes

Comment attendre la fin d'une méthode asynchrone ?

J'écris une application WinForms qui transfère des données vers un périphérique de classe USB HID. Mon application utilise l'excellente bibliothèque Generic HID v6.0 qui se trouve à l'adresse suivante aquí . En bref, lorsque j'ai besoin d'écrire des données sur le périphérique, c'est le code qui est appelé :

private async void RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        RequestToGetInputReport();
    }
}

Lorsque mon code sort de la boucle while, je dois lire des données à partir du dispositif. Cependant, l'appareil n'est pas en mesure de répondre immédiatement et je dois donc attendre le retour de cet appel avant de poursuivre. Tel qu'il existe actuellement, RequestToGetInputReport() est déclaré comme suit :

private async void RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

Pour ce que cela vaut, la déclaration pour GetInputReportViaInterruptTransfer() ressemble à ceci :

internal async Task<int> GetInputReportViaInterruptTransfer()

Malheureusement, je ne suis pas très familier avec le fonctionnement des nouvelles technologies async/await de .NET 4.5. J'ai lu un peu plus tôt le mot-clé await et cela m'a donné l'impression que l'appel à GetInputReportViaInterruptTransfer() à l'intérieur de RequestToGetInputReport() attendrait (et c'est peut-être le cas ?) mais il ne semble pas que l'appel à RequestToGetInputReport() lui-même attende car il semble que je rentre dans la boucle while presque immédiatement ?

Quelqu'un peut-il clarifier le comportement que je constate ?

288voto

Richard Cook Points 10763

La chose la plus importante à savoir sur async y await c'est que await n'a pas attendre que l'appel associé soit terminé. Qu'est-ce que await est de retourner le résultat de l'opération immédiatement et de manière synchrone. si l'opération est déjà terminée ou, s'il ne l'a pas fait, de programmer une continuation pour exécuter le reste de l'instruction async puis de rendre le contrôle à l'appelant. Lorsque l'opération asynchrone se termine, l'achèvement planifié s'exécute alors.

La réponse à la question spécifique posée dans l'intitulé de votre question est de bloquer sur une async la valeur de retour de la méthode (qui devrait être de type Task o Task<T> ) en appelant un Wait méthode :

public static async Task<Foo> GetFooAsync()
{
    // Start asynchronous operation(s) and return associated task.
    ...
}

public static Foo CallGetFooAsyncAndWaitOnResult()
{
    var task = GetFooAsync();
    task.Wait(); // Blocks current thread until GetFooAsync task completes
                 // For pedagogical use only: in general, don't do this!
    var result = task.Result;
    return result;
}

Dans cet extrait de code, CallGetFooAsyncAndWaitOnResult es un synchrone enveloppe autour d'une méthode asynchrone GetFooAsync . Toutefois, ce schéma est à éviter dans la plupart des cas, car il bloque un thread entier du pool de threads pendant la durée de l'opération asynchrone. Il s'agit d'une utilisation inefficace des divers mécanismes asynchrones exposés par les API qui font de gros efforts pour les fournir.

La réponse à "await" n'attend pas l'achèvement de l'appel contient plusieurs explications plus détaillées de ces mots-clés.

Pendant ce temps, les conseils de @Stephen Cleary au sujet de async void prises. Vous trouverez d'autres explications intéressantes à l'adresse suivante http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ y https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

157voto

Stephen Cleary Points 91731

Évitez async void . Faites revenir vos méthodes Task au lieu de void . Ensuite, vous pouvez await les.

Comme ça :

private async Task RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        await RequestToGetInputReport();
    }
}

private async Task RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

123voto

Ram chittala Points 619

La meilleure solution pour attendre AsynMethod jusqu'à ce que la tâche soit terminée est

var result = Task.Run(async() => await yourAsyncMethod()).Result;

5voto

Firas Nizam Points 340

Il suffit de mettre Wait() pour attendre que la tâche soit terminée.

GetInputReportViaInterruptTransfer().Wait();

0voto

John Melville Points 1140

Toutes les réponses ci-dessus sont correctes, vous ne devez jamais attendre une tâche de manière synchrone... sauf si vous y êtes obligé ! Parfois, vous voulez appeler une méthode asynchrone à l'intérieur d'une implémentation d'interface que vous ne contrôlez pas et il n'y a aucun moyen de faire "asynchrone jusqu'en bas".

Voici un petit cours pour au moins contenir les dégâts :

public class RunSynchronous
{
    public static void Do(Func<Task> func) => Task.Run(func).GetAwaiter().GetResult();
    public static T Do<T>(Func<Task<T>> func) => Task.Run(func).GetAwaiter().GetResult();
    public static void Do(Func<ValueTask> func) => Do(() => func().AsTask());
    public static T Do<T>(Func<ValueTask<T>> func) => Do(() => func().AsTask());
}

Cette classe utilise le motif .GetAwaiter.GetResult pour convertir réellement l'asynchrone en synchrone. Mais si vous faites cela tout seul dans WPF ou dans un autre SynchronizationContext lié à un thread particulier, vous vous bloquez. Ce code évite les blocages en transférant l'opération asynchrone vers le pool de threads qui n'est pas synchronisé à un thread particulier. Tant que vous ne devenez pas fou et que vous ne bloquez pas tous les threads du pool, tout devrait bien se passer.

L'utilisation est la suivante

    return RunSynchronous.Do(()=>AsyncOperation(a,b,c));

lancera AsynchronousOperation(a,b,c) sur le threadpool et attendra son retour. Tant que vous ne vous synchronisez pas explicitement avec le thread d'origine, tout devrait bien se passer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X