Невозможно одновременно вызывать асинхронные методы в правильном порядке в C#

Я изучаю асинхронные методы на C# и пытаюсь создать небольшую программу, которая вызывает несколько методов последовательно с небольшой задержкой. в конце операции я подсчитываю общее затраченное время. Вот мой код.

public static async Task<string> MakeCoffeeAsync()
{
    Thread.Sleep(3000);
    return("Coffee is Ready");           
}

public static async Task<string> ToastBreadAsync()
{
    Thread.Sleep(2000);
    return ("Bread is Toasted");
}

public static async Task<string> ApplyJamToBreadAsync()
{
    Thread.Sleep(1000);
    return ("Jam added to Bread");
}

public static async void MakeBreakfast()
    {
        Console.WriteLine("Making Breakfast async way");

        Stopwatch sw = new Stopwatch();
        sw.Start();

        Console.WriteLine(await MakeCoffeeAsync());
        Console.WriteLine(await ToastBreadAsync());
        Console.WriteLine(await ApplyJamToBreadAsync());            

        sw.Stop();
        Console.WriteLine("Normal Way - Total Time Taken " + sw.ElapsedMilliseconds / 1000 + " Seconds");
    }

Это работает правильно, и я получаю на выходе 6 секунд, что правильно.

Теперь я хочу, чтобы две задачи запускались одновременно. Одна задача может начать приготовление кофе (это займет 3 секунды), вторая задача — «Начать тост» (это займет 2 секунды), а когда тост будет готов, вторая задача нанесет джем на уже поджаренный хлеб (это займите 1 секунду). Последовательность важна для Toast. Джем следует наносить только после того, как тост готов.

Ожидаемый результат: общее затраченное время должно составлять 3 секунды. Потому что кофе и (Тост + Джем) будут работать параллельно.

Это то, что я пробовал.

    public static async void MakeBreakfastConcurrent()
    {
        Console.WriteLine("Making Breakfast Concurrent way");

        Stopwatch sw = new Stopwatch();
        sw.Start();
                    
        await Task.Run(() => Console.WriteLine(MakeCoffeeAsync()));

        await Task.Run(() => {
            Console.WriteLine(ToastBreadAsync());
            Console.WriteLine(ApplyJamToBreadAsync());
        });
     
        sw.Stop();
        Console.WriteLine("Normal Way - Total Time Taken " + sw.ElapsedMilliseconds / 1000 + " Seconds");
    }

Но это не работает так, как ожидалось. Когда я запускаю это, после "Making Breakfast Concurrent way" ничего не печатается

Что я делаю неправильно и как добиться правильного порядка выполнения одновременно? (вероятно, что-то вроде .then() в JS?)

Thread.Sleep не делает то, что вы думаете. (В любом случае, вообще говоря, никто не должен использовать Thread.Sleep).
Dai 05.08.2024 08:29

Вместо await Task.Delay() следует использовать Thread.Sleep().

shingo 05.08.2024 08:31

Я могу использовать Task.Delay, просто нужно смоделировать длительный метод, вот и все.

Bluemarble 05.08.2024 08:31

1. async/await вообще не подразумевает параллелизм - вместо этого async/await - это альтернативный синтаксис для определения конечного автомата продолжения - все остальное (параллелизм, многопоточность, синхронизация и т. д.) является проблемой времени выполнения (например, использование пул потоков или использование специального планировщика и т. д. 2. Thread.Sleep полностью блокирует поток, что лишает смысла его использование, await потому что сам поток заблокирован и поэтому не может ждать себя.

Dai 05.08.2024 08:32

@Bluemarble Thread.Sleep имитирует блокирующий вызов, тогда как async/awaitTask-возвращающие асинхронные API в целом в .NET) предназначены для неблокирующих вызовов. Нет никаких причин (и множество причин против) использовать async/await с вызовами методов блокировки (т. е. блокирующих вызовов).

Dai 05.08.2024 08:33

Каков фактический результат вашего второго примера? 5 секунд? Это ожидаемо (даже если игнорировать проблему сна, отмеченную выше), потому что вы сначала ждете кофе (3 секунды), а затем ждете тоста и джема, что вместе занимает 2 секунды (большее из 1 секунды и 2 секунд).

PMF 05.08.2024 08:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
6
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Ваши методы не асинхронны. Вы должны были получить предупреждение компилятора. Метод является асинхронным только в том случае, если хотя бы один из его операторов содержит ключевое слово await. Ключевое слово async в описании вашего метода само по себе не делает ваш метод асинхронным! Чтобы сделать ваши методы асинхронными, замените Thread.Sleep на await Task.Delay:

public static async Task<string> MakeCoffeeAsync()
{
    await Task.Delay(3000);
    return("Coffee is Ready");           
}

public static async Task<string> ToastBreadAsync()
{
    await Task.Delay(2000);
    return ("Bread is Toasted");
}

public static async Task<string> ApplyJamToBreadAsync()
{
    await Task.Delay(1000);
    return ("Jam added to Bread");
}

Чтобы одновременно запускать методы, сначала вызовите методы, не ожидая их. Затем дождитесь всех задач и распечатайте результаты:

Task<string> coffeTask = MakeCoffeeAsync();
Task<string> breadTask = ToastBreadAsync();
Task<string> jamTask = ApplyJamToBreadAsync();

await Task.WhenAll(coffeeTask, breadTask, jamTask);

string coffeeResult = await coffeTask;
string breadResult = await breadTask;
string jamResult = await jamTask;
Console.WriteLine(coffeeResult);
Console.WriteLine(breadResult );
Console.WriteLine(jamResult );

Онлайн-демо: https://dotnetfiddle.net/vhJLtk

Спасибо, попробовал, но тоже ничего не печатает. Программа завершается нормально, операторы console.writeline не выполняются.

Bluemarble 05.08.2024 08:39

Если вы щелкнете ссылку на dotnet-fiddle, вы увидите, что она действительно что-то печатает. Есть ли у вас асинхронный основной метод?

SomeBody 05.08.2024 08:43

Да, сейчас работаю! Основной метод не был асинхронным. Виноват.

Bluemarble 05.08.2024 08:52

Прежде всего

public static async Task<string> MakeCoffeeAsync()
{
    Thread.Sleep(3000);
    return("Coffee is Ready");           
}

Этот метод не делает ничего асинхронного, поэтому не должен быть асинхронным или возвращать задачу. Или вы можете использовать Task.Delay вместо Task.Sleep и удалить Task.Run.

Вторая проблема

public static async void MakeBreakfast()

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

static async Task<int> Main(string[] args) { }

Это должно гарантировать, что ваша программа завершится до того, как она появится.

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

Ищете алгоритм или подход для сравнения значений двух строк (.NET)
Определить, удален ли управляемый объект
Blazor EditForm: кнопка отключения приводит к тому, что все нетронутые поля помечаются как недействительные
.NET 8: MemoryCache SlidingExpiration без доступа
Изменение цвета фона панели задач на прозрачный через Win32 API или реестр в Windows 11
Проблема развертывания Blazor WASM Core в Azure
Netwonsoft JsonConvert.Deserialization выдает исключение JsonSerializationException: «Ошибка преобразования значения «Имя моего пользовательского объекта» в тип «System.Type»
Как обеспечить автоматическое масштабирование для CollectionView в объекте Popup из CommunityToolkit.Maui после его заполнения с помощью события нажатия кнопки на платформе Windows?
«Попытка чтения или записи защищенной памяти. Часто это признак того, что другая память повреждена». В программе на C#
Перемещение видеофайла из хранилища BLOB-объектов Azure на сайт Sharepoint на C#