Насколько правильно или не правильно делать ожидание Funcfunc при вызове await func.Invoke(string,string); в C# и почему?

Насколько правильно или не правильно делать ожидание Func<String,String,Task>func при вызове await func.Invoke(string,string); в C# и почему? А так же как вызовется этот метод в итоге? Синхронно или асинхронно?

public class A
{
    public Func<string, string, Task> ResiveMassage;
    public void Start()
    {
        B b = new B(ResiveMassage);
        while (true)
        {
           
            _ = Task.Run(async () => await b.Resive()).ConfigureAwait(false);
        }
    }
}
public class B
{
    string h = "Hello";
    string w = "World";
    Func<String, String, Task> ResiveMassage;
    public B(Func<String, String, Task> resiveMassage)
    {
        ResiveMassage = resiveMassage;
    }
    public async Task Resive()
    {
        await ResiveMassage?.Invoke(h,w);
       // await Task.Delay(5000);
    } 
}
public class C
{
    public void Strat()
    {
        A a = new A();
        a.ResiveMassage += ResiveMessageAsync;
        Thread thread = new Thread(new ThreadStart(()=>a.Start()));
        thread.Start();

    }
    async Task ResiveMessageAsync(string h, string w)
    {
        await Task.Run(() => Console.WriteLine($"{h} {w}"));
       // await Task.Delay(5000);
    }
}
class Program
{
    static void Main(string[] args)
    {
        C c = new C();
        c.Strat();
        Console.ReadLine();
    }
   
}

Ответы (1 шт):

Автор решения: aepot

Совершенно неважно, вызываете ли вы делегат или просто метод. И то и другое может завершиться синхронно, например.

Здесь даже мудрить с классами не надо, чтобы понять.

static async Task Main(string[] args)
{
    await DoA();
    await DoB();
    await DoC();
    Console.ReadLine();
}

private static async Task DoA()
{
    Console.WriteLine("Hello World");
}


private static async Task DoB()
{
    Console.WriteLine("Hello World");
    await Task.Delay(1000);
}

private static Task DoC()
{
    Console.WriteLine("Hello World");
    return Task.Delay(1000);
}

Здесь метод DoA() выполнится синхронно, остальные асинхронно. Но Console.WriteLine() везде выполнятся синхронно. Подробнее как это работает можно узнать здесь.

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

Что делает await Task.Run:

  • Спавнит новый поток или берет из пула потоков существующий, если уже есть такой
  • Запускает в этом потоке код
  • Возвращает Task, дающий возможность асинхронно дождаться конца выполнения кода в другом потоке

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

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

Далее, нужно понять, почему await без async нельзя, а наоборот - можно. Потому что первый генерирует машину состояний, а второй ее использует. Сгенерить машину состояний и не использовать ее конечно можно (метод DoA), но какой в этом смысл? Смысл в машине состояний появляется тогда, когда у нее существует более двух состояний. В противном случае достаточно либо обычного ветвления if-else, либо вообще код будет линейный.

Насколько правильно или не правильно делать ожидание Func<String,String,Task>func при вызове await func.Invoke(string,string) в C# и почему?

Этот вопрос по своей сути поставлен неправильно. Все зависит от реализации самого метода. Если метод совсем не ожидать, то зачем в принципе асинхронность нужна? Как я чуть выше писал, вся суть в асинхронности в ожидании await. Нет await - нет асинхронности, ну только если вы не обвешаетесь колбэками или континуациями .ContinueWith(). Ведь поведение await можно эмулировать с помощью написания самого обычного кода. async/await по своей сути - синтаксический "сахар", позволяющий писать с виду линейный код, когда внутри по своей сути он таковым не является. Для новичка выглядит как магия, но это до тех пор, пока он не закончит путать асинхронность с многопоточностью.

А так же как вызовется этот метод в итоге? Синхронно или асинхронно?

Вызовется или выполнится (завершится)?

  • Если "вызовется", то все методы вызываются синхронно, и вызвать метод асинхронно технически невозможно.
  • Если речь про "завершится", то совершенно неважно, что там этот метод возвращает, главное чтобы возвращал Task - первое условие асинхронности. У вас Task возвращается, это говорит о том, что метод может завершиться асинхронно, а может и синхронно. Надо смотреть, что там внутри, смотрим: await Task.Run(() => ... и больше ничего, но await есть, он возвращает тот самый заветный Task. Логичный вопрос: а метод Task.Run() может завершиться асинхронно, там есть await внутри? Грубо говоря "есть", но "нет", там есть специальный "ожидатель", код, который исходя из того, что там с запущенным потоком происходит, меняет внутреннее состояние того самого Task, который он вам вернул. Класс, управляющий состоянием Task, называется TaskCompletionSource. Вот весьма хорошее объяснение, как это работает.

Так как все-таки, синхронно или асинхронно?

Асинхронно, но это не точно. Потому что есть Task и есть await. А почему не точно? А потому что за запуск тасок отвечает TaskScheduler (ссылка .NET Blog), а он может вообще быть синхронным, это уж как вы пожелаете.

Асинхронное программирование - это просто. :)

→ Ссылка