c# - Не ожидание асинхронного вызова все еще асинхронно, верно?




.net asynchronous (2)

Извините если это глупый вопрос

Это не глупый вопрос. Это важный вопрос.

У меня есть сотрудник, который утверждает, что это делает вызов A синхронным, и он продолжает придумывать журналы Console.WriteLine, которые, казалось бы, подтверждают его точку зрения.

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

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

Итак, ваш коллега прав? Конечно они есть. Вызов A является синхронным, потому что все вызовы функций являются синхронными . Но тот факт, что они считают, что существует такая вещь, как «асинхронный вызов», означает, что они сильно ошибаются в том, как асинхронность работает в C #.

Если конкретно ваш коллега считает, что await M() каким-то образом делает вызов M() «асинхронным», то у вашего коллеги есть большое недоразумение. await оператора . Конечно, это сложный оператор, но это оператор, который работает со значениями. await M() и var t = M(); await t; var t = M(); await t; это одно и то же . Ожидание происходит после вызова, потому что await работает с возвращаемым значением . await НЕ является инструкцией для компилятора «генерировать асинхронный вызов M ()» или чего-либо подобного; нет такой вещи как «асинхронный вызов».

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

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

Это все, что await . Он просто проверяет содержимое задачи, и, если задача не завершена, он говорит: «Ну, мы не можем добиться какого-либо прогресса в этом рабочем процессе, пока эта задача не будет завершена, поэтому вернитесь к моей вызывающей стороне, которая найдет что-то еще для этого ЦП. сделать".

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

Это правильно. Мы синхронно вызываем A , и он возвращает Task . Код после вызова сайта не запускается, пока не вернется А. Интересно то, что A позволяет A возвращать незавершенную Task вызывающей стороне , и эта задача представляет узел в асинхронном рабочем процессе . Рабочий процесс уже асинхронный, и, как вы заметили, для A не имеет значения, что вы делаете с его возвращаемым значением после его возвращения; A понятия не имеет, будете ли вы await возвращенное Task или нет. A просто работает так долго, как может, и затем либо возвращает завершенную обычно задачу, либо завершенную исключительно задачу, либо возвращает незавершенную задачу. Но ничто из того, что вы делаете на сайте, не меняет этого.

Поскольку возвращаемое значение из A не требуется, нет необходимости ждать задачи на сайте вызова

Верный.

нет необходимости ждать задачи на месте вызова, пока кто-то в цепочке ожидает ее (что происходит в C).

Теперь ты потерял меня. Почему кто-то должен ждать Task возвращаемое A ? Скажите, почему вы считаете, что кто-то должен await этой Task , потому что у вас может быть ложное убеждение.

Мой коллега очень настойчив, и я начал сомневаться в себе. Мое понимание неверно?

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

Извините, если это глупый вопрос (или дубликат).

У меня есть функция A :

public async Task<int> A(/* some parameters */)
{
    var result = await SomeOtherFuncAsync(/* some other parameters */);

    return (result);
}

У меня есть другая функция B , вызывающая A но не использующая возвращаемое значение:

public Task B(/* some parameters */)
{
    var taskA = A(/* parameters */); // #1

    return (taskA);
}

Обратите внимание, что B не объявлен как async и не ожидает вызова A Вызов A не является вызовом "забыл и забыл" - B вызывается C примерно так:

public async Task C()
{
    await B(/* parameters */);
}

Обратите внимание, что на # 1 , нет await . У меня есть сотрудник, который утверждает, что это делает вызов A синхронным, и он продолжает придумывать журналы Console.WriteLine которые, казалось бы, подтверждают его точку зрения.

Я попытался указать, что только потому, что мы не ждем результатов внутри B , цепочка задач ожидается, и природа кода внутри A не меняется только потому, что мы не ожидаем этого. Поскольку возвращаемое значение из A не требуется, нет необходимости ждать задачи на месте вызова, пока кто-то в цепочке ожидает его (что происходит в C ).

Мой коллега очень настойчив, и я начал сомневаться в себе. Мое понимание неверно?


Ты прав. Создание задачи делает только это, и ей все равно, когда и кто будет ждать ее результата. Попробуйте поставить await Task.Delay(veryBigNumber); в SomeOtherFuncAsync и вывод консоли должен быть тем, что вы ожидаете.

Это называется eliding, и я предлагаю вам прочитать этот пост , где вы можете понять, почему вы должны или не должны делать такие вещи.

Также несколько минимальных (немного запутанных) примеров, копирующих ваш код, доказывающих, что вы правы:

class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine($"Start of main {Thread.CurrentThread.ManagedThreadId}");
            var task = First();
            Console.WriteLine($"Middle of main {Thread.CurrentThread.ManagedThreadId}");
            await task;
            Console.WriteLine($"End of main {Thread.CurrentThread.ManagedThreadId}");
        }

        static Task First()
        {
            return SecondAsync();
        }

        static async Task SecondAsync()
        {
            await ThirdAsync();
        }

        static async Task ThirdAsync()
        {
            Console.WriteLine($"Start of third {Thread.CurrentThread.ManagedThreadId}");
            await Task.Delay(1000);
            Console.WriteLine($"End of third {Thread.CurrentThread.ManagedThreadId}");
        }
    }

Это записывает Middle of main до End of third , доказывая, что это фактически асинхронно. Более того, вы можете (скорее всего) увидеть, что концы функций выполняются в другом потоке, чем остальная часть программы. И начала, и середина main всегда будут выполняться в одном и том же потоке, потому что они на самом деле являются синхронными (main запускает, вызывает цепочку функций, третьи возвращает (может возвращаться в строке с ключевым словом await ), а затем main продолжается, как если бы Когда-либо асинхронная функция не использовалась. Окончания после ключевых слов await в обеих функциях могут выполняться в любом потоке в ThreadPool (или в используемом вами контексте синхронизации).

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





async-await