Основываясь на этом вопросе, я пытаюсь заставить консольное приложение выполнять две задачи. Проблема в том, что консольное приложение завершает работу до завершения выполнения задач, и я не знаю, как мне дождаться их завершения.
static async Task Main(string[] args)
{
Action<string> action = async (string s) =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"{s} action executing");
await Task.Delay(2000);
}
};
var secondTask = Task.Run(() => action("second"));
var firstTask = Task.Run(() => action("first"));
Task.WhenAll(firstTask, secondTask).Wait();
}
Что, если вместо действий у меня будут настоящие асинхронные методы, которые мне нужно вызвать? Как мне дождаться завершения их выполнения?
@SomeBody, потому что Действие не возвращает Задачу, не так ли? Это блестяще! Пожалуйста, добавьте это как ответ, и я отмечу это.
Если бы у вас была реальная асинхронная функция, вы бы написали await MyFunction1(), await MyFunction2(). Если вы хотите запускать их параллельно, вы бы написали await Task.WhenAll(MyFunction1(), MyFunction2())





Action<string> action = async (string s) создает делегат async void action(string s), которого нельзя ожидать. Это эквивалентно написанию:
static async void action(string s) =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"{s} action executing");
await Task.Delay(2000);
}
}
Либо используйте Func<Task>, либо, что еще лучше, используйте локальную функцию:
static async Task Main(string[] args)
{
async Task action(string s) =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"{s} action executing");
await Task.Delay(2000);
}
};
var secondTask = Task.Run(() => action("second"));
var firstTask = Task.Run(() => action("first"));
await Task.WhenAll(firstTask, secondTask);
}
Использование Func<string,Task> также работает, но каждый раз при вызове метода выделяется новый делегат:
static async Task Main(string[] args)
{
Func<string,Task> action = async (string s) =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"{s} action executing");
await Task.Delay(2000);
}
};
var secondTask = Task.Run(() => action("second"));
var firstTask = Task.Run(() => action("first"));
await Task.WhenAll(firstTask, secondTask);
}
Обновлять
Как отмечает Фузи, Task.Run в этом случае можно исключить. В этом случае Task.Run в любом случае возвращает внутреннюю задачу.
var secondTask = action("second");
var firstTask = action("first");
await Task.WhenAll(firstTask, secondTask);
Task.Run может быть полезен, если action содержит значительную блокирующую работу, прежде чем он начнет выполнять асинхронную работу, например:
async Task action(string stock)
{
var trade=RunSuperStockPredictionModel(stock);
await client.PostTradeAsync(trade);
}
var secondTask = Task.Run(() => action("GOOG"));
var firstTask = Task.Run(() => action("MSFT"));
В этом случае лучше выделить блокирующий компонент в отдельный метод.
Не могли бы вы также избавиться от оберток Task.Run(() => ...) вокруг вызовов action()?
@phuzi конечно, на самом деле Task.Run сам возвращает внутреннюю задачу напрямую. Я предполагаю, что функция action — это всего лишь заполнитель для операции, требующей реального времени. Task.Run было бы полезно, если бы action содержал код блокировки перед любыми асинхронными операциями.
«Использование Func<string,Task> также работает, но»: выдает «Параметр 1 объявлен как тип «строка», но должен быть «System.Threading.Tasks.Task»»
@Luuk (facepalm) Комментарий правильный, код неправильный. Должно быть Func<string,Task> action = async (string s)
@phuzi, если цель состоит в том, чтобы распараллелить две операции, тогда необходим Task.Run. Task.Run вызывает делегат action в ThreadPool, поэтому две операции while начинают выполняться сразу, если только ThreadPool не насыщен. Без Task.Run операции будут запускаться синхронно в текущем потоке, поэтому их распараллеливание не гарантируется (оно становится зависимым от их реализации, а не от доступности ThreadPool, что в целом является более безопасным вариантом).
@TheodorZoulias только в том случае, если операции имеют синхронный компонент. Если это просто асинхронные вызовы, они будут выполняться одновременно. В общем in general нет, это зависит от того, что делает код. Использовать Task.Run(()=>client.GetAsJsonAsync()) просто расточительно. Если перед этим вам предстоит проделать значительную работу, да, нужно Task.Run
Что именно тратится на Task.Run? Если вы считаете, что выгрузка вызовов в ThreadPool расточительна, вам следует перестать использовать Parallel.ForEachAsync, потому что он делает именно это. Когда намерением является «параллелизм», реализация, передающая это намерение, равна ThreadPool. Если вы не хотите использовать ThreadPool, хорошо, но не называйте это параллелизмом, назовите это асинхронным параллелизмом.
Вам следует использовать Func<string,Task> вместо Action<string>.