Использование await Task.Factory.StartNew для метода вернется немедленно

Я запускаю следующий код (Консольное приложение C#7.1), и я не могу понять, почему разница в поведении.

Если я жду регулярного вызова асинхронного метода или Task.Run - он работает как положено (то есть приложение не возвращается сразу). Но если я использую Task.Factory.StartNew - он вернется сразу же, без кода на самом деле.

Как ни странно - если я использую StartNew, но внутри метода удалим await, он не вернется сразу...

Проблема: это немедленно возвращается:

static async Task Main(string[] args)
{
    await Task.Factory.StartNew(DisplayCurrentInfo);
}

static async Task DisplayCurrentInfo()
{
    await WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

то есть - я не смогу увидеть что-либо распечатанное на консоли, и консоль уже будет закрыта.

Нет проблем: это не возвращается сразу:

static async Task Main(string[] args)
{
    await DisplayCurrentInfo(); // or await Task.Run(DisplayCurrentInfo);
}

static async Task DisplayCurrentInfo()
{
    await WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

Странно: это тоже не сразу возвращается

static async Task Main(string[] args)
{
    await Task.Factory.StartNew(DisplayCurrentInfo); 
}

static async Task DisplayCurrentInfo()
{
    WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

WaitAndApologize:

static async Task WaitAndApologize()
{
    // Task.Delay is a placeholder for actual work.  
    await Task.Delay(2000);
    // Task.Delay delays the following line by two seconds.  
    Console.WriteLine("\nSorry for the delay. . . .\n");
}

3 ответа

Решение

Если вы используете Task.Factory.StartNew(MethodThatReturnsTask) вы получите обратно Task<Task<T>> или же Task<Task> в зависимости от того, возвращает ли метод общую задачу или нет.

Конечным результатом является то, что у вас есть 2 задачи:

  1. Task.Factory.StartNew порождает задачу, которая вызывает MethodThatReturnsTaskдавайте назовем эту задачу "Задача А"
  2. MethodThatReturnsTask в вашем случае возвращает Task, давайте назовем это "Задача B", это означает, что перегрузка StartNew который обрабатывает это используется, и конечный результат состоит в том, что вы получаете задание A, которое оборачивает задачу B.

Для "правильного" ожидания этих задач требуется 2 ожидания, а не 1. Ваше одиночное ожидание просто ожидает Задачу A, а это означает, что когда она возвращается, Задача B все еще выполняется в ожидании завершения.

Чтобы наивно ответить на ваш вопрос, используйте 2 awaits:

await await Task.Factory.StartNew(DisplayCurrentInfo);

Однако сомнительно, почему вам нужно создавать задачу, чтобы запустить другой асинхронный метод. Вместо этого вам гораздо лучше использовать второй синтаксис, где вы просто ждете метод:

await DisplayCurrentInfo();

Мнение следующее: В общем, как только вы начали писать асинхронный код, используя Task.Factory.StartNew или любой из его родственных методов должен быть зарезервирован для случаев, когда вам нужно порождать поток (или что-то подобное) для вызова чего-то, что не является асинхронным параллельно с чем-то другим. Если вам не нужен именно этот шаблон, лучше его не использовать.

Когда вы звоните Task.Factory.StartNew(DisplayCurrentInfo); вы используете подпись:

public Task<TResult> StartNew<TResult>(Func<TResult> function)

Теперь, так как вы передаете метод с подписью Task DisplayCurrentInfo() тогда TResult тип Task,

Другими словами, вы возвращаете Task<Task> от Task.Factory.StartNew(DisplayCurrentInfo) и тогда вы ожидаете внешнего задания, которое возвращает Task - и ты этого не ждешь. Следовательно это завершается немедленно.

await Task.Factory.StartNew(DisplayCurrentInfo);

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

await Task.Factory.StartNew( await DisplayCurrentInfo); //Imaginary code to understand

В третьем примере WaitAndApologize не ожидается, но его блокирует спящий поток (Full Thread заблокирован).

Только с помощью кода мы не можем сказать, что фабрика задач создала новый поток или просто работает в существующем потоке. Если он работает в том же потоке, весь поток блокируется, поэтому у вас возникает ощущение, что код ожидает await Task.Factory.StartNew(DisplayCurrentInfo);, но на самом деле это не происходит.

Если он работает в потоке difnet, вы не получите тот же результат, что и выше.

Другие вопросы по тегам