Task.Wait не ожидает завершения асинхронного метода - PullRequest
2 голосов
/ 04 июня 2019

Вот код:

static async Task Main(string[] args)
{
    var t = new Task(async () => await AsyncTest());
    t.Start();
    t.Wait();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}

Я ожидаю, что t.Wait() на самом деле будет ждать завершения метода AsyncTest, и результат будет:

Method finished 
Main finished

На самом деле вывод имеет только Main finished. Wait () завершается в тот момент, когда вы нажимаете await Task.Delay(2000) внутри AsyncTest. То же самое происходит, если вы замените t.Wait() на await t / await Task.WhenAll(t) / Task.WaitAll(t).

Решение состоит в том, чтобы переписать метод в синхронную реализацию или вызвать Wait () непосредственно в AsyncTest (). Однако вопрос в том, почему это так странно работает?

P.S. Это упрощенная версия кода. Я пытался добиться отложенного выполнения задачи. На самом деле объект Task создается одной частью программы, а затем выполняется другой частью после определенного условия.

UPD: перезапись var t = new Task(async () => await AsyncTest()) на var t = new Task(()=> AsyncTest().Wait()) также устраняет проблему. Хотя я до сих пор не совсем понимаю, почему Task не работает правильно с async / await внутри делегата.

Ответы [ 4 ]

6 голосов
/ 04 июня 2019

Я до сих пор не совсем понимаю, почему Task не работает правильно с async / await внутри делегата.

Поскольку конструктор Task используется только для создания Делегированные задачи - т.е. задачи, представляющие синхронный код для запуска.Поскольку код является синхронным, ваша async лямбда рассматривается как async void лямбда, что означает, что экземпляр Task не будет асинхронно ждать завершения AsyncTest.

Еще к сути, конструктор Task никогда и никогда не должен использоваться ни в каком коде, нигде и по любой причине .У него буквально ноль действительных вариантов использования.

Хорошей заменой для Task.Task является Task.Run, что действительно понимает async лямбды.

ВМоя настоящая программа Task отложена на выполнение.Объект задачи создается в одном месте, а затем - после выполнения определенного условия другой частью программы.

В этом случае используйте асинхронный делегат .В частности, Func<Task>.

static async Task Main(string[] args)
{
    Func<Task> func = AsyncTest;

    // Later, when we're ready to run.
    await func();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}
0 голосов
/ 04 июня 2019

A quote от @JonSkeet:

Ваш асинхронный метод просто возвращает void, что означает, что не существует простого способа что-либо ожидать его завершения.(Вы почти всегда должны избегать использования асинхронных void-методов. Они действительно доступны только для подписки на события.)

Итак, посмотрите на эту строку вашего кода:

var t = new Task(async () => await AsyncTest());

Посмотрите на сигнатуру Task конструкторов:

    public Task(Action action);
    public Task(Action action, CancellationToken cancellationToken);       
    public Task(Action action, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state);
    public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state, CancellationToken cancellationToken);
    public Task(Action<object> action, object state, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions);

Все они Actions и, как вы знаете, Action имеет void тип возврата.

static async Task Main(string[] args)
{
    // best way to do it
    await AsyncTest();


    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    // Don't use thread sleep, await task delay is fine
    // Thread.Sleep(2000);

    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}
0 голосов
/ 04 июня 2019

отметьте это

public static async Task Main(string[] args)
{
    var t = Task.Factory.StartNew(async () => await AsyncTest());
    //t.Start();
    t.Result.Wait();    
    t.Wait();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    //Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}

и эту ссылку

0 голосов
/ 04 июня 2019

Не используйте var task = new Task(...); task.Start();, используйте просто Task.Run.

В первом случае используется конструктор Task(Action action), так что вы фактически не ожидаете асинхронного метода.Если вы проверите real C # без синтаксического асинхронного ожидания, вы увидите AsyncVoidMethodBuilder.В случае Task.Run вы используете Task.Run(Func<Task> func), поэтому вы получаете «настоящие» задачи.

Так что, если вы хотите использовать конструктор, вы должны использовать подход из комментариев: new Task<Task>.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...