Я пытаюсь запустить асинхронный метод из другого асинхронного метода и убедиться, что оба они завершились успешно. Как и в примере ниже, я добавляю задержку 1000
мс в Function1()
и задержку 3000
мс в Function2()
, то есть задача Main()
должна завершиться первой. Кроме того, я использую .Net6.0
, чтобы запустить этот фрагмент.
using System;
using System.Threading.Tasks;
using System.Threading;
public class Program
{
static async Task Main(string[] args)
{
var inst = new SomeClass();
// inst.Functon1(); // Method is called but not waited till its completion
await inst.Functon1(); // Seems this blocks this Main method itself
Console.WriteLine("finished");
}
}
public class SomeClass
{
public async Task Functon1(){
await Task.Delay(1000); // should have await to block
Console.WriteLine("SomeClass In Func1");
await Functon2();
}
private async Task Functon2(){
await Task.Delay(3000);
Console.WriteLine("SomeClass In Func2");
await Functon3();
}
private async Task Functon3(){
Console.WriteLine("SomeClass In Func3");
}
}
Вывод должен быть таким, как показано ниже, в том же порядке, что и ожидание. Если await
убрать из Main()
, печатаются только готовые, но не остальные.
finished
SomeClass In Func1
SomeClass In Func2
SomeClass In Func3
PS. Я пробовал вызывать inst.Functon1()
с помощью .ConfigureAwait(true)
и ConfigureAwait(false)
, но в методе async
это мало что изменило.
ОП не ожидает, что он будет напечатан первым, они этого хотят. т. е. они хотят, чтобы Functon1
назывался асинхронным, но (в их текущем коде) это означает, что main
завершается без ожидания ожидаемых вызовов. т. е. без await Functon1
вывод просто «закончен». Использование await Functon1
приводит к блокировке main
(«завершено» — это последняя строка вывода, а не первая). Им нужна Task.Run
, обернутая Functon1()
, которую можно дождаться в конце main
(т. е. согласно моему ответу :) )
В том виде, в котором вы это закодировали, «закончено» будет выводиться последним. Если вы хотите, чтобы он выводился первым, вам следует поместить его перед другими вызовами. Так что ваш вопрос действительно не имеет смысла. Он работает так, как задумано. Вам также не следует делать то, что предложил Делтикс в своем ответе — не могу придумать сценарий, в котором это было бы полезно.
Я не уверен, говорит ли нам ОП, что они ожидают, что текущий код сначала отобразит finished
, или они спрашивают, как сначала распечатать finished
. Их смущает поведение этого кода или они не знают, как изменить код, чтобы он работал должным образом.
Ожидание было четко упомянуто в самом первом посте Output should be like below, in the exact same order is the expectation.
. Не уверен, что кто-то тратит время на то, чтобы прочитать пост полностью, а затем задавать связанные с ним вопросы.
@oceano1970526 — Должно быть, в Канаде говорят на другом диалекте английского языка, чем в Австралии — хотя я думал, что в этом плане мы очень похожи, но, возможно, я ошибаюсь. Я неоднократно читал эту строку и до сих пор могу интерпретировать ее двояко. В сознании автора это ясно, но страдает читатель. Может быть, вы будете так любезны, разъясните свою мысль?
Я бы посоветовал попрактиковаться или попробовать этот фрагмент, хотя он выглядит простым, и можно удивиться, сколько вариаций он может принести. Недавно кто-то из ведущей компании на этой планете поспорил со мной о том, почему ожидание не блокируется. Причина, по которой я разместил фрагмент и решение, состоит в том, чтобы задать вопрос более кратко. Хуже всего то, что люди кому-то проголосовали против такого важного вопроса. Должен признаться, что я воздержался от комментариев по поводу имени пользователя относительно того, как оно связано с самим комментарием, лол.
@oceano1970526 — Не могли бы вы ответить на мой вопрос о ваших ожиданиях? Кроме того, await
блокирует выполнение вашего кода в await
, но не блокирует поток. Это действительно зависит от того, на чем вы сосредоточены: «как ожидание не блокируется».
Вопрос, конечно, в том, чтобы сначала получить ожидаемый результат с помощью finished
. Сказав это, я бы посоветовал прочитать комментарии в строке await
и строке перед ней? Конечно, не стесняйтесь возиться с этими строками, чтобы получить ожидаемый результат. Пожалуйста, дайте мне знать, если что-то еще не ясно.... :) ....
@gunr2171 ..... Мне нравится работать над созданием нескольких потоков из основного потока, если созданные потоки выполняются долго, мне нравится, чтобы они завершались независимо от того, работает ли еще основной поток или нет.... PS - прихожу из опыта, отличного от C#, и это работает на других языках программирования, таких как javscript.....
Чтобы вызвать async Functon1
без блокировки, но при этом иметь возможность await
завершиться в какой-то другой момент, вы вызываете функцию без await
и сохраняете ссылку на возвращаемый Task
. Затем это можно будет await
проверить перед возвращением из main
.
Что-то подобное:
static async Task Main(string[] args)
{
var inst = new SomeClass();
var func1 = inst.Functon1();
Console.WriteLine("finished");
await func1;
}
Функции async
возвращают Task
, инкапсулируя выполнение самой функции.
Вызов функции async
без await
эквивалентен вызову неасинхронной функции с оболочкой Task.Run
:
var task = Task.Run(() => SomeSyncFunc());
Для функции, отмеченной async
, оболочка Task
создается автоматически.
Сохраняя ссылку на Task
, вы получаете возможность await
завершить ее на более позднем этапе выполнения задачи.
Я подозреваю, что ваши задержки 1s
и 3s
предназначены для имитации длительных действий, но для вашего исследования и экспериментов здесь, поскольку все вызовы await
выполняются, это излишне замедляет ваш цикл обратной связи.
Вы можете уменьшить эти задержки до 10ms
и 30ms
(или даже устранить их); это не повлияет на порядок завершения вызовов.
Вот рабочий пример, демонстрирующая решение:
using System;
using System.Threading.Tasks;
public class Program
{
static async Task Main(string[] args)
{
var inst = new SomeClass();
func1 = inst.Functon1();
Console.WriteLine("finished");
await func1;
}
}
public class SomeClass
{
public async Task Functon1(){
await Task.Delay(10);
Console.WriteLine("SomeClass In Func1");
await Functon2();
}
private async Task Functon2(){
await Task.Delay(30);
Console.WriteLine("SomeClass In Func2");
await Functon3();
}
private async Task Functon3(){
await Task.Run(() => Console.WriteLine("SomeClass In Func3"));
}
}
Я также изменил Functon3
, чтобы сделать это async
(хотя и искусственно). Эта модификация демонстрирует использование оболочки Task.Run
вокруг синхронного вызова, позволяющего его await
обрабатывать.
Это было сделано просто для того, чтобы линтер был доволен функцией async
, не содержащей асинхронного кода. :)
Почему var func1 = Task.Run(() => inst.Functon1());
вместо var func1 = inst.Functon1()
?
Я как раз собирался оставить точно такой же комментарий. Task.Run
не добавляет никакой ценности коду.
Потому что прошло более 3 лет с тех пор, как я прикоснулся к C#. :) Хороший улов, спасибо. Я обновлю ответ и поиграю.
Без обид, но если бы прошло «3+ года с тех пор, как я прикоснулся к C#», я бы не пытался отвечать на асинхронные вопросы, какими бы хорошими ни были ваши намерения.
@Deltics - Я бы хотел поменять private async Task Functon3a() { await Task.Run(() => Console.WriteLine("SomeClass In Func3")); }
на private Task Functon3b() => Task.Run(() => Console.WriteLine("SomeClass In Func3"));
. Обычно await Task.Run
— это просто пустая трата времени.
@MitchWheat, мы здесь для того, чтобы судить об ответе, а не об опыте отвечающего. Несмотря на то, что ОП не прикасался к C# более 3 лет, я не вижу в этом ответе ничего плохого. И это все, что имеет значение.
@Теодор Зулиас, ты подразумеваешь, что система репутации этого SO бесполезна?
@MitchWheat, пока мы говорим об оценке ответа, да, репутация отвечающего не имеет значения. Плохой ответ, написанный Джоном Скитом, по-прежнему остается плохим ответом, а хороший ответ, написанный Джо Средней, по-прежнему остается хорошим ответом.
@MitchWheat Это противоречит тому, что вы сказали раньше. У ОП могла быть высокая репутация только более 3 лет назад. В любом случае, мы должны судить ответ по существу в том виде, в каком он сейчас редактируется.
«Вызов асинхронной функции без await эквивалентен вызову неасинхронной функции с оболочкой Task.Run» не совсем верно: без Task.Run
тогда весь код до первого await
в вызываемой функции будет выполняться синхронно.
Имеет смысл судить о публикациях, основываясь на них самих, а не на исторических данных или репутации, в отличие от того, что делает ИИ или что-то подобное, что делает их более рациональными и актуальными.
Вывод должен быть таким, как показано ниже, в том же порядке, что и ожидание.
finished SomeClass In Func1 SomeClass In Func2 SomeClass In Func3
Получить этот вывод невозможно, поскольку консольное приложение завершает работу сразу после завершения точки входа Main
. Основной поток консольного приложения поддерживает работу процесса. Как только основному потоку больше нечего делать и при условии, что никакие другие переднего плана потоки не были запущены и еще живы, процесс завершится почти сразу. Таким образом, после того, как "finished"
будет напечатан, вы больше не получите никаких результатов.
Я пробовал вызывать
inst.Functon1()
с помощью.ConfigureAwait(true)
иConfigureAwait(false)
, но в методеasync
это мало что изменило.
На самом деле это не имело никакого значения, немаленького. ConfigureAwait
влияет на поведение await
только при наличии окружающего SynchronizationContext
или окружающего не по умолчанию TaskScheduler
. Консольные приложения не оснащены ни тем, ни другим, что вы можете подтвердить экспериментально, распечатав SynchronizationContext.Current и TaskScheduler.Current. Таким образом, ConfigureAwait
абсолютно не влияет на ваши await
здесь.
Дерзко, но так верно!
В консоли и в ASP .Net 8.0 только TaskScheduler.Current
печатается в System.Threading.Tasks.ThreadPoolTaskScheduler
. Да, я смог работать асинхронно в ASP .Net, используя Parallel.ForEachAsync
без await
, при условии, что серверный процесс все еще работает. Если серверный процесс остановится почти сразу, завершат ли потоки в Parallel.ForEachAsync
выполнение.
@oceano1970526 ThreadPoolTaskScheduler
— это TaskScheduler
по умолчанию. Вы можете подтвердить это, проверив на равенство: TaskScheduler.Current == TaskScheduler.Default
. Что касается принципа «выстрелил и забыл» в ASP.NET, прочитайте этот ответ Стивена Клири. Почти всегда это плохая идея.
Кстати, приведенные здесь пункты в некоторой степени отражают богатство в реальном мире и то, как трудно рабочему классу или бедным пробиться в общество. У меня всего около 100 баллов, сначала я потерял около 4 баллов и набрал в общей сложности только 2 балла, несмотря на то, что это правильный вопрос и человеку, у которого около 41 тыс. очков, чтобы ответить на него правильно.
@oceano1970526 ну, задать хороший вопрос, который принесет вам очки репутации, - непростая задача. Я задавал себе вопросы, которые были плохо восприняты (пример ), поэтому говорю по опыту. Обычно легче заработать очки репутации, публикуя ответы, чем задавая вопросы.
Почему вы ожидаете, что сначала будет напечатано «законченное»? «Кажется, это блокирует сам метод Main» да, вы ждете его завершения, прежде чем продолжить. dotnetfiddle.net/9g0X1H