Как вызвать метод asyn c, который гарантированно будет синхронизирован - PullRequest
1 голос
/ 04 марта 2020

У меня есть метод, который более или менее выглядит следующим образом:

public async Task PossiblyAsync(bool amIlucky)
{
    if (amIlucky)
        await Task.Delay(9000);

    return;
}

В некоторых случаях я точно знаю, что в нем не будет вызываться asyn c codepathes, но все же мне нужно сделать все асин c на самый верх. Что само по себе не является проблемой, но мне интересно, если это приведет к ненужным накладным расходам, когда идеально синхронизированный c код притворяется асинхронным c, оборачивая все результаты в Task<T> и выполняя «кто знает, что еще» .

Кроме того, я бы очень хотел, чтобы он не переходил между потоками. Я не уверен, может ли это произойти, тем более что в ядре asp net отсутствует контекст синхронизации. Но опять же, я не совсем понимаю, что такое контекст синхронизации:)

Каков самый правильный способ вызова такого метода?

Ответы [ 4 ]

3 голосов
/ 04 марта 2020

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

Это не обычно слишком плохо; государственный машиностроитель пытается оптимизировать ситуацию и избегать всего ненужного - возвращая Task.CompletedTask, когда это возможно Однако для методов, которые возвращают Task<T>, это не так легко доступно - вы можете вместо этого переключиться на ValueTask<T>, поскольку это более эффективно в случае "часто синхронно, но необходимо вернуть значение". В очень горячем коде (обычно в библиотеках ввода-вывода) здесь часто добавляется дополнительный слой, чтобы избежать конечного автомата полностью , пока мы не узнаем, что мы собираемся асинхронно c, но это ниша и, вероятно, за пределы того, что вам нужно.


В качестве примера фрагментов «избегать государственного аппарата» (и снова подчеркивая, что большинству кода application никогда не понадобится этот уровень оптимизации), рассмотрим :

ValueTask<Foo> SomeMethodAsync(...)
{
    ValueTask<Bar> theThing = DoTheThingAsync(...); // some other things that we need
    if (theThing.IsCompletedSuccessfully)
    {
        return new ValueTask<Foo>(ProcessResults(theThing.Result));
    }
    else
    {
        return Awaited(theThing);
    }

    static async ValueTask<Foo> Awaited(ValueTask<Bar> incomplete)
        => ProcessResults(await incomplete.ConfigureAwait(false));

    static Foo ProcessResults(Bar bar) {...whatever...}
3 голосов
/ 04 марта 2020

Вы можете переписать метод, чтобы избежать генерации кода, который генерируется при создании метода asyn c:

public Task PossiblyAsync(bool amIlucky)
{
    if (amIlucky)
        return Task.Delay(9000);

    return Task.CompletedTask;
}

Вы можете сделать это, так как ваш метод не должен делать что-нибудь после завершения Task.Delay.

Task.CompletedTask всегда находится в завершенном состоянии, поэтому, когда ваш метод вызывается с помощью false, выделения памяти не будет.

1 голос
/ 05 марта 2020

Интересно, будут ли внесены некоторые ненужные издержки, когда идеально синхронизированный c код притворяется асинхронным c, оборачивая все результаты в Task и выполняя «кто знает что».

Да; Task<T> создано. Это единственная нагрузка, когда возможно асинхронный код выполняется синхронно. Обычно вы можете полностью игнорировать эти накладные расходы. Если этот код называется строго в al oop или что-то в этом роде, то вы бы хотели избежать этих дополнительных выделений, что можно сделать, используя ValueTask<T> вместо Task<T>.

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

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

Каков наиболее правильный способ вызова такого метода?

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

1 голос
/ 04 марта 2020

Если все await ожидают того, что уже выполнено, это приводит к небольшим накладным расходам. В целом функция async/await постоянно оптимизировалась в течение многих лет, чтобы внести как можно меньше накладных расходов. В частности, конечные автоматы async созданы для struct s, чтобы их можно было размещать в стеке как локальные переменные, и они повышаются до кучи только тогда, когда необходимо создать продолжение. Затраты на создание class Task<> объектов уменьшаются с помощью struct ValueTask<>, где это возможно, и так далее. Вам не нужно беспокоиться, если только профилирование не покажет вам большую проблему в конкретном c месте.

...