Як дочекатися завершення методу асинхронізації?


138

Я пишу програму WinForms, яка передає дані на пристрій класу USB HID. У моїй програмі використовується чудова бібліотека HID v6.0, яку можна знайти тут . Коротше кажучи, коли мені потрібно записати дані на пристрій, це код, який викликається:

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

Коли мій код випадає з циклу while, мені потрібно прочитати деякі дані з пристрою. Однак пристрій не зможе відповісти відразу, тому мені потрібно чекати, коли цей дзвінок повернеться, перш ніж продовжувати. Як це існує, RequestToGetInputReport () оголошується так:

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

Для чого це варто, декларація для GetInputReportViaInterruptTransfer () виглядає так:

internal async Task<int> GetInputReportViaInterruptTransfer()

На жаль, я не дуже знайомий з роботою нових технологій асинхрон / очікування в .NET 4.5. Я трохи почитав раніше про ключове слово, що очікує, і це створило мені враження, що дзвінок на GetInputReportViaInterruptTransfer () всередині RequestToGetInputReport () зачекає (а може, і є?), Але це не схоже на дзвінок на RequestToGetInputReport () сама чекає, тому що я, здається, знову входжу в цикл while майже відразу?

Хтось може уточнити поведінку, яку я бачу?

Відповіді:


131

Уникайте async void. Попросіть ваші методи повернутися Taskзамість void. Тоді ви можете awaitїх.

Подобається це:

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

1
Дуже приємно, дякую. Я почухав голову на аналогічне питання , а різниця в тому, щоб змінити voidдо Taskтак само , як ви сказали.
Джеремі

8
Це незначна річ, але щоб дотримуватися конвенції, обидва способи повинні були до їх імен додати Async, наприклад RequestToGetInputReportAsync ()
tymtam

6
а що робити, якщо абонент є функцією Main?
симбіонт

14
@symbiont: Тоді використовуйGetAwaiter().GetResult()
Стівен Клірі

4
@AhmedSalah Позначає Taskвиконання методу, тому returnзначення розміщуються Task.Resultі ставляться виключення Task.Exception. З void, компілятору нікуди не розміщувати винятку, тому вони просто заново піднімаються на нитку пулу потоків.
Стівен Клірі

229

Найголовніше, про що потрібно знати, asyncі awaitце те, await що не чекає завершення пов'язаного дзвінка. Що awaitполягає в тому, щоб повернути результат операції негайно та синхронно, якщо операція вже завершена, або, якщо цього не відбулося, запланувати продовження для виконання залишку asyncметоду, а потім повернути контроль абоненту. Коли асинхронна операція завершиться, тоді виконується заплановане завершення.

Відповідь на конкретне запитання у заголовку вашого запитання полягає у блокуванні asyncзворотного значення методу (яке має бути типу Taskабо Task<T>) шляхом виклику відповідного Waitметоду:

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

У цьому фрагменті коду CallGetFooAsyncAndWaitOnResultє синхронна обгортка навколо асинхронного методу GetFooAsync. Однак більшою мірою слід уникати цього шаблону, оскільки він буде блокувати цілу нитку пулу ниток протягом тривалості асинхронної операції. Це неефективне використання різних асинхронних механізмів, що піддаються API, що докладають великих зусиль для їх забезпечення.

Відповідь на "очікувати" не чекає завершення дзвінка, має кілька, більш детальних, пояснень цих ключових слів.

Тим часом, вказівки @Stephen Cleary про async voidзатримки. Інші приємні пояснення того, чому можна знайти на веб- сайті http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ та https://jaylee.org/archive/ 2012/07/08 / c-остро-асинхрон-підказки-і-трюки-частина-2-async-void.html


18
Мені здається корисним думати (і говорити) про це awaitяк про "асинхронному очікуванні" - тобто він блокує метод (при необхідності), але не нитку . Тому є сенс говорити про RequestToSendOutputReport"очікування", RequestToGetInputReportнавіть якщо це не блокування .
Стівен Клірі

@Richard Cook - дуже дякую за додаткове пояснення!
bmt22033

10
Це повинно бути прийнятою відповіддю, оскільки воно чіткіше відповідає на власне запитання (тобто, як обробляти потік блоку методом асинхронізації).
csvan

найкраще рішення - зачекати асинхронізацію, поки завдання не буде виконано - var результат = Task.Run (async () => {повернути очікуйте yourMethod ();}).
Ram chittala

70

Найкраще рішення зачекати AsynMethod до завершення завдання

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

15
Або це для вашої асинхронної "порожнечі": Task.Run (async () => {очікуйте вашогоAsyncMethod ();}). Wait ();
Jiří Herník

1
Яка вигода від цього у вашомуAsyncMethod (). Результат?
Джастін Дж Старк

1
Простий доступ до властивості .Result насправді не чекає, поки завдання закінчиться виконанням. Насправді, я вважаю, що це кидає виняток, якщо його викликають до того, як завдання буде виконано. Я думаю, що перевага обгортання цього виклику Task.Run () полягає в тому, що, як згадує Річард Кук нижче, "очікування" насправді не чекає завершення завдання, але використання виклику .Wait () блокує весь пул потоків. . Це дозволяє (синхронно) запустити метод асинхронізації на окремому потоці. Трохи заплутаний, але це так.
Лукас Леблан

приємно кидаючи результат, якраз те, що мені потрібно
Геррі

Швидке нагадування ECMA7, подібне до функцій assync (), або очікує на звичайну роботу в середовищі до ECMA7.
Мботет

0

Ось вирішення питання використання прапора:

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

-1

просто покладіть Wait (), щоб зачекати, поки завдання не буде виконано

GetInputReportViaInterruptTransfer().Wait();


Це блокує поточну нитку. Тому зазвичай це погано робити.
Pure.Krome

-4

Насправді я вважаю це кориснішим для функцій, які повертають IAsyncAction.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;

-5

Наступний фрагмент показує спосіб забезпечити завершення очікуваного методу перед поверненням до абонента. ЯКЩО б я не сказав, що це хороша практика. Будь ласка, відредагуйте мою відповідь поясненнями, якщо ви думаєте про інше.

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

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")

Покірники, хочете пояснити, як я запитав у відповіді? Тому що це добре працює, наскільки я знаю ...
Джертер

3
Проблема полягає в тому, що єдиний спосіб, як ви можете зробити await+, Console.WriteLine- це перетворитися на "а" Task, який відмовляється від контролю над ними. тож ваше «рішення» в кінцевому рахунку дасть результат Task<T>, який не вирішує проблему. Рухаєшся Task.Waitбуде фактично припинити обробку (з тупиковими можливостями і т.д.). Іншими словами, awaitнасправді не чекає, він просто поєднує дві асинхронно виконувані частини в єдину Task(яку хтось може дивитись або чекати)
Рубен Бартелінк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.