Использование 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 задачи:
Task.Factory.StartNew
порождает задачу, которая вызываетMethodThatReturnsTask
давайте назовем эту задачу "Задача А"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, вы не получите тот же результат, что и выше.