Comment attendre la fin de la méthode asynchrone?

138

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 peut être trouvée ici . En un mot, lorsque j'ai besoin d'écrire des données sur l'appareil, 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 certaines données de l'appareil. Cependant, l'appareil ne peut pas répondre tout de suite. Je dois donc attendre le retour de cet appel avant de continuer. Tel qu'il existe actuellement, RequestToGetInputReport () est déclaré comme ceci:

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

Pour ce que ça vaut, la déclaration de GetInputReportViaInterruptTransfer () ressemble à ceci:

internal async Task<int> GetInputReportViaInterruptTransfer()

Malheureusement, je ne suis pas très familier avec le fonctionnement des nouvelles technologies async / await dans .NET 4.5. J'ai fait une petite lecture plus tôt sur le mot-clé await et cela m'a donné l'impression que l'appel à GetInputReportViaInterruptTransfer () à l'intérieur de RequestToGetInputReport () attendrait (et peut-être qu'il le fait?) Mais cela ne ressemble pas à l'appel à RequestToGetInputReport () lui-même attend parce que je semble rentrer dans la boucle while presque immédiatement?

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

bmt22033
la source

Réponses:

131

Evitez async void. Faites revenir vos méthodes Taskau lieu de void. Ensuite, vous pouvez awaiteux.

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();
}
Stephen Cleary
la source
1
Très bien merci. Je grattais la tête sur un problème similaire et la différence était de changer voidà Tasktout comme vous l' aviez dit.
Jeremy du
8
C'est une chose mineure, mais pour suivre la convention, les deux méthodes devraient avoir Async ajouté à leurs noms, par exemple RequestToGetInputReportAsync ()
tymtam
6
et que faire si l'appelant est la fonction principale?
symbiont
14
@symbiont: Ensuite, utilisezGetAwaiter().GetResult()
Stephen Cleary
4
@AhmedSalah Le Taskreprésente l'exécution de la méthode - les returnvaleurs sont donc placées Task.Resultet les exceptions sont placées Task.Exception. Avec void, le compilateur n'a nulle part où placer les exceptions, elles sont donc simplement relancées sur un thread de pool de threads.
Stephen Cleary
229

La chose la plus importante à savoir asyncet awaitc'est que await cela n'attend pas la fin de l'appel associé. Ce qui awaitfait est de renvoyer le résultat de l'opération immédiatement et de manière synchrone si l'opération est déjà terminée ou, si ce n'est pas le cas, de planifier une continuation pour exécuter le reste de la asyncméthode, puis de renvoyer le contrôle à l'appelant. Une fois l'opération asynchrone terminée, l'achèvement planifié s'exécutera.

La réponse à la question spécifique dans le titre de votre question est de bloquer asyncla valeur de retour d' une méthode (qui doit être de type Taskou Task<T>) en appelant une Waitméthode appropriée :

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, se CallGetFooAsyncAndWaitOnResulttrouve un wrapper synchrone autour de la méthode asynchrone GetFooAsync. Cependant, ce modèle doit être évité pour la plupart car il bloquera un thread de pool de threads entier 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 déploient de grands efforts pour les fournir.

La réponse à "attendre" n'attend pas la fin de l'appel a plusieurs explications plus détaillées de ces mots-clés.

Pendant ce temps, les conseils de @Stephen Cleary sur les async voidprises. D'autres explications intéressantes sur pourquoi peuvent être trouvées sur http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ et https://jaylee.org/archive/ 2012/07/08 / c-sharp-async-trucs-et-astuces-partie-2-async-void.html

Richard Cook
la source
18
Je trouve utile de penser (et de parler) awaitcomme d'une "attente asynchrone" - c'est-à-dire que cela bloque la méthode (si nécessaire) mais pas le thread . Il est donc logique de parler d ' RequestToSendOutputReport«attente» RequestToGetInputReportmême si ce n'est pas une attente bloquante .
Stephen Cleary
@Richard Cook - merci beaucoup pour l'explication supplémentaire!
bmt22033
10
Cela devrait être la réponse acceptée, car elle répond plus clairement à la question réelle (c'est-à-dire comment bloquer par thread sur une méthode asynchrone).
csvan
la meilleure solution est d'attendre asynchrone jusqu'à ce que la tâche soit terminée est var result = Task.Run (async () => {return wait yourMethod ();}). Result;
Ram chittala
70

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

var result = Task.Run(async() => await yourAsyncMethod()).Result;
Ram Chittala
la source
15
Ou ceci pour votre asynchrone "void": Task.Run (async () => {wait yourAsyncMethod ();}). Wait ();
Jiří Herník
1
Quel est l'avantage de ceci sur yourAsyncMethod (). Result?
Justin J Stark le
1
Le simple fait d'accéder à la propriété .Result n'attend pas la fin de l'exécution de la tâche. En fait, je pense qu'il lève une exception s'il est appelé avant qu'une tâche ne soit terminée. Je pense que l'avantage d'envelopper cela dans un appel Task.Run () est que, comme le mentionne Richard Cook ci-dessous, "wait" n'attend pas réellement la fin d'une tâche, mais l'utilisation d'un appel .Wait () bloque tout votre pool de threads . Cela vous permet d'exécuter (de manière synchrone) une méthode asynchrone sur un thread distinct. Un peu déroutant, mais ça y est.
Lucas Leblanc
beau lancer le résultat là-bas, juste ce dont j'avais besoin
Gerry
Rappel rapide ECMA7 assync () ou attendre ne fonctionnera pas dans l'environnement pré-ECMA7.
Mbotet du
0

Voici une solution de contournement utilisant un indicateur:

//outside your event or method, but inside your class
private bool IsExecuted = false;

private async Task MethodA()
{

//Do Stuff Here

IsExecuted = true;
}

.
.
.

//Inside your event or method

{
await MethodA();

while (!isExecuted) Thread.Sleep(200); // <-------

await MethodB();
}
choquant lemi
la source
-1

il suffit de mettre Wait () pour attendre la fin de la tâche

GetInputReportViaInterruptTransfer().Wait();

Firas Nizam
la source
Cela bloque le thread actuel. C'est donc généralement une mauvaise chose à faire.
Pure.Krome le
-5

L'extrait de code suivant montre un moyen de s'assurer que la méthode attendue se termine avant de retourner à l'appelant. CEPENDANT, je ne dirais pas que c'est une bonne pratique. Veuillez modifier ma réponse avec des explications si vous pensez le contraire.

public async Task AnAsyncMethodThatCompletes()
{
    await SomeAsyncMethod();
    DoSomeMoreStuff();
    await Task.Factory.StartNew(() => { }); // <-- This line here, at the end
}

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")
Jerther
la source
Downvoters, voulez-vous expliquer, comme je l'ai demandé dans la réponse? Parce que cela fonctionne bien pour autant que je sache ...
Jerther
3
Le problème est que la seule façon de faire le await+ le Console.WriteLineest de devenir un Task, ce qui abandonne le contrôle entre les deux. ainsi votre «solution» produira finalement un Task<T>, qui ne résout pas le problème. Faire un Task.Waittestament arrêtera en fait le traitement (avec des possibilités de blocage, etc.). En d'autres termes, awaitn'attend pas réellement, il combine simplement deux parties exécutables de manière asynchrone en une seule Task(que quelqu'un peut regarder ou attendre)
Ruben Bartelink
-5

En fait, j'ai trouvé cela plus utile pour les fonctions qui retournent IAsyncAction.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
Barış Tanyeri
la source