Я изучаю асинхронные методы на 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?)
Вместо await Task.Delay()
следует использовать Thread.Sleep()
.
Я могу использовать Task.Delay, просто нужно смоделировать длительный метод, вот и все.
1. async/await вообще не подразумевает параллелизм - вместо этого async
/await
- это альтернативный синтаксис для определения конечного автомата продолжения - все остальное (параллелизм, многопоточность, синхронизация и т. д.) является проблемой времени выполнения (например, использование пул потоков или использование специального планировщика и т. д. 2. Thread.Sleep
полностью блокирует поток, что лишает смысла его использование, await
потому что сам поток заблокирован и поэтому не может ждать себя.
@Bluemarble Thread.Sleep
имитирует блокирующий вызов, тогда как async
/await
(и Task
-возвращающие асинхронные API в целом в .NET) предназначены для неблокирующих вызовов. Нет никаких причин (и множество причин против) использовать async
/await
с вызовами методов блокировки (т. е. блокирующих вызовов).
Каков фактический результат вашего второго примера? 5 секунд? Это ожидаемо (даже если игнорировать проблему сна, отмеченную выше), потому что вы сначала ждете кофе (3 секунды), а затем ждете тоста и джема, что вместе занимает 2 секунды (большее из 1 секунды и 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 не выполняются.
Если вы щелкнете ссылку на dotnet-fiddle, вы увидите, что она действительно что-то печатает. Есть ли у вас асинхронный основной метод?
Да, сейчас работаю! Основной метод не был асинхронным. Виноват.
Прежде всего
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) { }
Это должно гарантировать, что ваша программа завершится до того, как она появится.
Thread.Sleep
не делает то, что вы думаете. (В любом случае, вообще говоря, никто не должен использоватьThread.Sleep
).