Асинхронный и синхронный методы

192
16 сентября 2017, 22:10

Имеется некий асинхронный метод:

public async Task<Foo> GetFooAsync();

мне также нужен его синхронный аналог

public Foo GetFoo();

Мне не очень хотелось бы полностью переписывать GetFooAsync и создавать на его основе синхронную версию метода. Достаточно ли будет сделать например так?:

public Foo GetFoo() 
{
    return GetFooAsync().GetAwaiter().GetResult();
}

или у такой реализации "в лоб" есть подводные камни и так лучше не делать?

Answer 1

Обратите внимание на вот этот вопрос: Зависает оператор `await` в оконном приложении / программа висит при вызове Task.Result или Wait

Там показана основная проблема таких вот вызовов GetResult(). И там же есть решение проблемы "но что делать если очень надо" через временный контекст синхронизации:

public Foo GetFoo() 
{
    using (var ctx = new QueueSynchronizationContext())
    {
        var task = GetFooAsync();
        ctx.WaitFor(task);
        return task.GetAwaiter().GetResult();
    }
}

Но это решение подходит только для однопоточных контекстов синхронизации, при выполнении в неограниченном пуле потоков правильнее будет вызвать task.GetAwaiter().GetResult(); напрямую. В ограниченных же пулах потоков все еще сложнее - нельзя использовать QueueSynchronizationContext потому что он убьет параллельность - но нельзя и использовать блокирующее ожидание, потому что оно займет один поток.

Универсальным способом будет Task.Run(() => GetFooAsync()).GetAwaiter().GetResult(), но этот способ смотрится странно (и опять-таки, несет проблемы когда пул потоков ограничен).

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

Answer 2

В общем случае нельзя просто взять и вызывать GetResult() / .Result в асинхронном коде, т.к. это с большой вероятностью приведет к дедлоку:

  1. Ваш код ждет завершения GetFooAsync, блокируя поток.
  2. Один из await внутри GetFooAsync ждет освобождения потока, чтобы выполнить на нем остаток GetFooAsync.

Возможные варианты обхода дедлока:

  1. Использовать .ConfigureAwait(false) на всех асинхронных вызовах внутри вашего асинхронного метода (и на вызовах внутри вызовов из вашего метода). Тогда продолжнения для кода внутри метода будут выполняться на потоках из пула, без сохранения контекста.
  2. Принудительно запускать ваш асинхронный метод в потоке из пула: string code = Task.Run(GetFooAsync).Result;

Но лучше так не делать, и тянуть асинхронность до самого верха.

Answer 3

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

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

Но это не панацея, слишком много если в таком случае. Проще переписать синхронный отдельно.

READ ALSO
Почему ошибка считаются: AddCapsInfo(); и Caps?

Почему ошибка считаются: AddCapsInfo(); и Caps?

Декларация функции - 7 параметров:

180
Выделить (X) закрыть на вкладке tabPage c#

Выделить (X) закрыть на вкладке tabPage c#

Здравствуйте, в самом конструкторе Visual Studio, в открытом окне когда наводишь курсор на вкладку (X) закрыть, данный крестик выделяется, крестик...

346
Marshal.GetExceptionPointers и WPF-овский DispatcherUnhandledException

Marshal.GetExceptionPointers и WPF-овский DispatcherUnhandledException

Доброго времени сутокЕсть WPF-приложение + дампер(MiniDumpWriteDump)

172