Ответ
Вызов асинхронного метода из синхронного контекста — это антипаттерн, которого следует избегать. Лучшее решение — сделать всю цепочку вызовов асинхронной. Если это невозможно, используйте следующие подходы с пониманием их рисков.
1. Task.Run для CPU-bound операций
Используйте, если асинхронный метод выполняет интенсивные вычисления, а не I/O.
public void SyncMethod()
{
// Запускаем задачу в пуле потоков и синхронно ожидаем результат
Task.Run(() => AsyncCpuBoundOperation()).GetAwaiter().GetResult();
}
private async Task AsyncCpuBoundOperation()
{
await Task.Run(() => HeavyComputation());
}
2. .GetAwaiter().GetResult() для I/O операций (с осторожностью)
Может привести к взаимоблокировкам (deadlock), особенно в контекстах с SynchronizationContext (например, UI-поток в WPF/WinForms или HttpContext в старых версиях ASP.NET).
public void SyncMethod()
{
// Риск дедлока! Не используйте в UI-потоке или ASP.NET (не Core).
AsyncIoMethod().GetAwaiter().GetResult();
}
3. Запуск без ожидания (Fire-and-forget) Подходит только для фоновых операций, результат которых не нужен вызывающему коду. Опасен потерей исключений.
public void SyncMethod()
{
// Просто запускаем задачу. Исключения могут быть не обработаны.
_ = AsyncMethod();
}
Ключевые рекомендации:
- Избегайте
Task.Wait()иTask.Result— они оборачивают исключения вAggregateExceptionи также подвержены дедлокам. - В ASP.NET Core используйте асинхронные контроллеры и методы (
async Task<IActionResult>). Синхронные обертки нарушают масштабируемость. - В библиотечном коде предоставляйте как асинхронные (
DoWorkAsync), так и синхронные (DoWork) версии методов, если это необходимо, где синхронная версия является оберткой. - Рассмотрите
IAsyncDisposableиIAsyncEnumerable<T>для полной асинхронной интеграции.