Вызов асинхронного метода из другого асинхронного метода

Я пытаюсь запустить асинхронный метод из другого асинхронного метода и убедиться, что оба они завершились успешно. Как и в примере ниже, я добавляю задержку 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 это мало что изменило.

Почему вы ожидаете, что сначала будет напечатано «законченное»? «Кажется, это блокирует сам метод Main» да, вы ждете его завершения, прежде чем продолжить. dotnetfiddle.net/9g0X1H

gunr2171 11.07.2024 02:02

ОП не ожидает, что он будет напечатан первым, они этого хотят. т. е. они хотят, чтобы Functon1 назывался асинхронным, но (в их текущем коде) это означает, что main завершается без ожидания ожидаемых вызовов. т. е. без await Functon1 вывод просто «закончен». Использование await Functon1 приводит к блокировке main («завершено» — это последняя строка вывода, а не первая). Им нужна Task.Run, обернутая Functon1(), которую можно дождаться в конце main (т. е. согласно моему ответу :) )

Deltics 11.07.2024 02:13

В том виде, в котором вы это закодировали, «закончено» будет выводиться последним. Если вы хотите, чтобы он выводился первым, вам следует поместить его перед другими вызовами. Так что ваш вопрос действительно не имеет смысла. Он работает так, как задумано. Вам также не следует делать то, что предложил Делтикс в своем ответе — не могу придумать сценарий, в котором это было бы полезно.

mason 11.07.2024 02:21

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

Enigmativity 11.07.2024 02:27

Ожидание было четко упомянуто в самом первом посте Output should be like below, in the exact same order is the expectation. . Не уверен, что кто-то тратит время на то, чтобы прочитать пост полностью, а затем задавать связанные с ним вопросы.

oceano1970526 12.07.2024 00:25

@oceano1970526 — Должно быть, в Канаде говорят на другом диалекте английского языка, чем в Австралии — хотя я думал, что в этом плане мы очень похожи, но, возможно, я ошибаюсь. Я неоднократно читал эту строку и до сих пор могу интерпретировать ее двояко. В сознании автора это ясно, но страдает читатель. Может быть, вы будете так любезны, разъясните свою мысль?

Enigmativity 12.07.2024 01:40

Я бы посоветовал попрактиковаться или попробовать этот фрагмент, хотя он выглядит простым, и можно удивиться, сколько вариаций он может принести. Недавно кто-то из ведущей компании на этой планете поспорил со мной о том, почему ожидание не блокируется. Причина, по которой я разместил фрагмент и решение, состоит в том, чтобы задать вопрос более кратко. Хуже всего то, что люди кому-то проголосовали против такого важного вопроса. Должен признаться, что я воздержался от комментариев по поводу имени пользователя относительно того, как оно связано с самим комментарием, лол.

oceano1970526 12.07.2024 19:16

@oceano1970526 — Не могли бы вы ответить на мой вопрос о ваших ожиданиях? Кроме того, await блокирует выполнение вашего кода в await, но не блокирует поток. Это действительно зависит от того, на чем вы сосредоточены: «как ожидание не блокируется».

Enigmativity 13.07.2024 03:58

Вопрос, конечно, в том, чтобы сначала получить ожидаемый результат с помощью finished. Сказав это, я бы посоветовал прочитать комментарии в строке await и строке перед ней? Конечно, не стесняйтесь возиться с этими строками, чтобы получить ожидаемый результат. Пожалуйста, дайте мне знать, если что-то еще не ясно.... :) ....

oceano1970526 15.07.2024 18:47

@gunr2171 ..... Мне нравится работать над созданием нескольких потоков из основного потока, если созданные потоки выполняются долго, мне нравится, чтобы они завершались независимо от того, работает ли еще основной поток или нет.... PS - прихожу из опыта, отличного от C#, и это работает на других языках программирования, таких как javscript.....

oceano1970526 18.07.2024 19:34
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
10
120
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Чтобы вызвать 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()?

gunr2171 11.07.2024 02:22

Я как раз собирался оставить точно такой же комментарий. Task.Run не добавляет никакой ценности коду.

Enigmativity 11.07.2024 02:23

Потому что прошло более 3 лет с тех пор, как я прикоснулся к C#. :) Хороший улов, спасибо. Я обновлю ответ и поиграю.

Deltics 11.07.2024 02:24

Без обид, но если бы прошло «3+ года с тех пор, как я прикоснулся к C#», я бы не пытался отвечать на асинхронные вопросы, какими бы хорошими ни были ваши намерения.

Mitch Wheat 11.07.2024 02:50

@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 — это просто пустая трата времени.

Enigmativity 11.07.2024 03:09

@MitchWheat, мы здесь для того, чтобы судить об ответе, а не об опыте отвечающего. Несмотря на то, что ОП не прикасался к C# более 3 лет, я не вижу в этом ответе ничего плохого. И это все, что имеет значение.

Theodor Zoulias 11.07.2024 06:44

@Теодор Зулиас, ты подразумеваешь, что система репутации этого SO бесполезна?

Mitch Wheat 11.07.2024 07:01

@MitchWheat, пока мы говорим об оценке ответа, да, репутация отвечающего не имеет значения. Плохой ответ, написанный Джоном Скитом, по-прежнему остается плохим ответом, а хороший ответ, написанный Джо Средней, по-прежнему остается хорошим ответом.

Theodor Zoulias 11.07.2024 07:08

@MitchWheat Это противоречит тому, что вы сказали раньше. У ОП могла быть высокая репутация только более 3 лет назад. В любом случае, мы должны судить ответ по существу в том виде, в каком он сейчас редактируется.

Charlieface 11.07.2024 11:31

«Вызов асинхронной функции без await эквивалентен вызову неасинхронной функции с оболочкой Task.Run» не совсем верно: без Task.Run тогда весь код до первого await в вызываемой функции будет выполняться синхронно.

Charlieface 11.07.2024 11:32

Имеет смысл судить о публикациях, основываясь на них самих, а не на исторических данных или репутации, в отличие от того, что делает ИИ или что-то подобное, что делает их более рациональными и актуальными.

oceano1970526 12.07.2024 01:04
Ответ принят как подходящий

Вывод должен быть таким, как показано ниже, в том же порядке, что и ожидание.

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 здесь.

Дерзко, но так верно!

Enigmativity 11.07.2024 08:02

В консоли и в ASP .Net 8.0 только TaskScheduler.Current печатается в System.Threading.Tasks.ThreadPoolTaskScheduler. Да, я смог работать асинхронно в ASP .Net, используя Parallel.ForEachAsync без await, при условии, что серверный процесс все еще работает. Если серверный процесс остановится почти сразу, завершат ли потоки в Parallel.ForEachAsync выполнение.

oceano1970526 11.07.2024 19:28

@oceano1970526 ThreadPoolTaskScheduler — это TaskScheduler по умолчанию. Вы можете подтвердить это, проверив на равенство: TaskScheduler.Current == TaskScheduler.Default. Что касается принципа «выстрелил и забыл» в ASP.NET, прочитайте этот ответ Стивена Клири. Почти всегда это плохая идея.

Theodor Zoulias 11.07.2024 20:32

Кстати, приведенные здесь пункты в некоторой степени отражают богатство в реальном мире и то, как трудно рабочему классу или бедным пробиться в общество. У меня всего около 100 баллов, сначала я потерял около 4 баллов и набрал в общей сложности только 2 балла, несмотря на то, что это правильный вопрос и человеку, у которого около 41 тыс. очков, чтобы ответить на него правильно.

oceano1970526 11.07.2024 23:59

@oceano1970526 ну, задать хороший вопрос, который принесет вам очки репутации, - непростая задача. Я задавал себе вопросы, которые были плохо восприняты (пример ), поэтому говорю по опыту. Обычно легче заработать очки репутации, публикуя ответы, чем задавая вопросы.

Theodor Zoulias 12.07.2024 00:25

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