Не ожидание асинхронного вызова все еще асинхронно, верно? - PullRequest
13 голосов
/ 21 сентября 2019

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

У меня есть функция 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).

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

Ответы [ 2 ]

21 голосов
/ 21 сентября 2019

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

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

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

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

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

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

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

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

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

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

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

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

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

Правильно.

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

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

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

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

6 голосов
/ 21 сентября 2019

Вы правы.Создание задачи делает только это, и ей все равно, когда и кто будет ждать ее результата.Попробуйте вставить 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 не займет очень много времени и фактически завершится синхронно, все это будет выполняться в одном потоке. Более того, даже если он будет работать асинхронно, он может все работатьв одном потоке. Нет правила, утверждающего, что асинхронная функция будет использовать более одного потока, она вполне может просто выполнить некоторую другую работу, ожидая завершения какой-либо задачи ввода-вывода.

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