Страницы

Поиск по вопросам

суббота, 7 декабря 2019 г.

Асинхронный и синхронный методы [дубликат]

#c_sharp #async_await


        
             
                
                    
                        
                            This question already has answers here:
                            
                        
                    
                
                        
                            Синхронный и асинхронный методы и дублирование кода
                                
                                    (2 ответа)
                                
                        
                                Closed 2 года назад.
            
                    
Имеется некий асинхронный метод: 

public async Task GetFooAsync();


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

public Foo GetFoo();


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

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


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


Ответы

Ответ 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(), но этот способ смотрится странно (и опять-таки, несет проблемы когда пул потоков ограничен). Наверное, проще всего просто выбрать только один метод и использовать только его. Но если вы пишите библиотеку - возможно, правильнее всего будет написать алгоритм два раза, и в блокирующем варианте, и в асинхронном.

Ответ 2



В общем случае нельзя просто взять и вызывать GetResult() / .Result в асинхронном коде, т.к. это с большой вероятностью приведет к дедлоку: Ваш код ждет завершения GetFooAsync, блокируя поток. Один из await внутри GetFooAsync ждет освобождения потока, чтобы выполнить на нем остаток GetFooAsync. Возможные варианты обхода дедлока: Использовать .ConfigureAwait(false) на всех асинхронных вызовах внутри вашего асинхронного метода (и на вызовах внутри вызовов из вашего метода). Тогда продолжнения для кода внутри метода будут выполняться на потоках из пула, без сохранения контекста. Принудительно запускать ваш асинхронный метод в потоке из пула: string code = Task.Run(GetFooAsync).Result; Но лучше так не делать, и тянуть асинхронность до самого верха.

Ответ 3



Так в лоб лучше не делать. И я настоятельно рекомендовал переписать отдельно на синхронный метод. Но уж если слишком лень и точно известно, что таск не уйдет в дэдлок, то можно попробовать так. var task = MyAsyncMethod(); var result = task.WaitAndUnwrapException(); Но это не панацея, слишком много если в таком случае. Проще переписать синхронный отдельно.

Комментариев нет:

Отправить комментарий