Задача не ожидает ожидания в консольном приложении

Основываясь на этом вопросе, я пытаюсь заставить консольное приложение выполнять две задачи. Проблема в том, что консольное приложение завершает работу до завершения выполнения задач, и я не знаю, как мне дождаться их завершения.

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();
}

Что, если вместо действий у меня будут настоящие асинхронные методы, которые мне нужно вызвать? Как мне дождаться завершения их выполнения?

Вам следует использовать Func<string,Task> вместо Action<string>.

SomeBody 21.05.2024 14:28

@SomeBody, потому что Действие не возвращает Задачу, не так ли? Это блестяще! Пожалуйста, добавьте это как ответ, и я отмечу это.

meJustAndrew 21.05.2024 14:32

Если бы у вас была реальная асинхронная функция, вы бы написали await MyFunction1(), await MyFunction2(). Если вы хотите запускать их параллельно, вы бы написали await Task.WhenAll(MyFunction1(), MyFunction2())

Magnus 21.05.2024 14:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
120
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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 21.05.2024 14:40

@phuzi конечно, на самом деле Task.Run сам возвращает внутреннюю задачу напрямую. Я предполагаю, что функция action — это всего лишь заполнитель для операции, требующей реального времени. Task.Run было бы полезно, если бы action содержал код блокировки перед любыми асинхронными операциями.

Panagiotis Kanavos 21.05.2024 14:42

«Использование Func<string,Task> также работает, но»: выдает «Параметр 1 объявлен как тип «строка», но должен быть «System.Threading.Tasks.Task»»

Luuk 21.05.2024 14:59

@Luuk (facepalm) Комментарий правильный, код неправильный. Должно быть Func<string,Task> action = async (string s)

Panagiotis Kanavos 21.05.2024 15:03

@phuzi, если цель состоит в том, чтобы распараллелить две операции, тогда необходим Task.Run. Task.Run вызывает делегат action в ThreadPool, поэтому две операции while начинают выполняться сразу, если только ThreadPool не насыщен. Без Task.Run операции будут запускаться синхронно в текущем потоке, поэтому их распараллеливание не гарантируется (оно становится зависимым от их реализации, а не от доступности ThreadPool, что в целом является более безопасным вариантом).

Theodor Zoulias 21.05.2024 20:25

@TheodorZoulias только в том случае, если операции имеют синхронный компонент. Если это просто асинхронные вызовы, они будут выполняться одновременно. В общем in general нет, это зависит от того, что делает код. Использовать Task.Run(()=>client.GetAsJsonAsync()) просто расточительно. Если перед этим вам предстоит проделать значительную работу, да, нужно Task.Run

Panagiotis Kanavos 22.05.2024 08:48

Что именно тратится на Task.Run? Если вы считаете, что выгрузка вызовов в ThreadPool расточительна, вам следует перестать использовать Parallel.ForEachAsync, потому что он делает именно это. Когда намерением является «параллелизм», реализация, передающая это намерение, равна ThreadPool. Если вы не хотите использовать ThreadPool, хорошо, но не называйте это параллелизмом, назовите это асинхронным параллелизмом.

Theodor Zoulias 22.05.2024 09:14

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

Верните новую задачу с возвращаемым значением, используя конструктор задач с асинхронным действием
Async/await обещает альтернативу. Переписать ожидание на обещание
Основное статическое свойство «текст», изолированное от актера, не может быть изменено из неизолированного контекста; это ошибка в Swift 6
Свободный шаблон с асинхронными методами
Почему новые промисы используются рядом с ожиданием, хотя в них нет необходимости?
Типы возвращаемых результатов против асинхронных и ожидающих
Как цикл «for await» в асинхронной «очереди» может вызвать «удаление из очереди» перед «постановкой в ​​очередь»? Он возвращает new Promise() перед помещением в очередь
В C# async/await влияет ли длина задержки на возврат управления вызывающему абоненту?
Функция Async останавливается в середине
Превратите неожиданное необработанное ошибочное обещание в предупреждение @processTicksAndRejections (созданное ошибкой выдачи в "then")