Страницы

Поиск по вопросам

понедельник, 23 декабря 2019 г.

Почему Task, отдельно созданный через конструктор и отложено выполненный, игнорирует оператор await?

#c_sharp #async_await


Обнаружил странное. Если создаю Task таким образом

var task = new Task(async action);
task.Start();


то при вызове любого асинхронного метода (await Task.Delay(), await new WebClient().DownloadStringAsync(),
etc.) игнорируется оператор await и код сразу же возвращается к основному потоку.

Если же Task конструирую рекомендуемым во всех статьях способом (через Task.Run(async
action)), то всё работает, как ожидается.

Пример кода, иллюстрирующий проблему:

class Program
{
    static async Task Main()
    {
        // way1 - it's working
        var goodTask = Task.Run(async () => await ProblemMethod("goodTask"));
        await goodTask;
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} :: {nameof(goodTask)} is finished.");

        // way2 - it fails without any exceptions on line with GetRequestStreamAsync
        var poorTask = new Task(async () => await ProblemMethod("poorTask"));
        poorTask.Start();
        await poorTask;
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} :: {nameof(poorTask)} is finished.");

        Console.ReadLine();
    }

    static async Task ProblemMethod(string taskName)
    {
        Console.WriteLine($"{taskName} :: {DateTime.Now:HH:mm:ss} :: It's a first
line.");
        await Task.Delay(2000);
        Console.WriteLine($"{taskName} :: {DateTime.Now:HH:mm:ss} :: It's a last line.");
    }
}


В консоль выводится:

goodTask :: 18:52:50 :: It's a first line.
goodTask :: 18:52:52 :: It's a last line.
18:52:52 :: goodTask is finished.
poorTask :: 18:52:52 :: It's a first line.
18:52:52 :: poorTask is finished.
poorTask :: 18:52:54 :: It's a last line.


Почему такое странное поведение во втором случае ("it's a last line" выводится уже
после возвращения в основной поток? Оператор await ведь присутствует...

P.S. Да, я в курсе, что рекомендуемый способ работы с тасками в TAP - это сразу же
запускать их при конструировании (через Task.Run или Task.Factory.StartNew), но как
следует поступать, если необходимо отделять конструирование тасков от команды на их
исполнение, т.е., если надо реализовать их отложенное выполнение?
    


Ответы

Ответ 1



Проблема в том, что конструктор new Task() принимает только Action и Action с разными комбинациями токена отмены, стейта и т.д, но нет перегрузки, принимающей Func или Func чтобы можно было дождаться переданной асинхронной лямбды. В то время как Task.Run имеет такую перегрузку. Таким образом - у вас возникает классическая проблема ожидания async void. Если мы сделаем var result = await poorTask; то фактически мы получим void и ошибку компиляции. Для того чтобы дождаться внутренней задачи, обычно нужно сделать Unwrap или добавить еще один await, но в данном случае мы получаем void и дождаться внутренней задачи не можем. Как описал в комментарии @PetSerAl нужно использовать generic версию Task чтобы иметь возможность дождаться внутренней задачи.

Комментариев нет:

Отправить комментарий