Использовать Task.Run () в синхронном методе, чтобы избежать тупиковой остановки в асинхронном методе?

ОБНОВЛЕНИЕ objective этого вопроса – получить простой ответ о Task.Run() и тупиковой ситуации. Я очень понимаю теоретические рассуждения о том, что вы не смешиваете асинхронные и синхронизирующие сигналы, и я принимаю их близко к сердцу. Я не выше изучения новых вещей от других; Я стараюсь делать это, когда смогу. Есть только времена, когда всем нужен парень, это технический ответ …

У меня есть метод Dispose() который должен вызвать метод async. Поскольку 95% моего кода является асинхронным, рефакторинг не лучший выбор. Наличие IAsyncDisposable (среди других функций), которое поддерживается каркасом, было бы идеальным, но мы пока не находимся. Поэтому, в то же время, мне нужно найти надежный способ вызова асинхронных методов из синхронного метода без взаимоблокировки.

Я бы предпочел не использовать ConfigureAwait(false) потому что это оставляет ответственность, разбросанную по всему моему коду, чтобы вызываемый мог вести себя определенным образом, только если вызывающий является синхронным. Я бы предпочел что-то сделать в синхронном методе, так как это девиантный bugger.

Прочитав комментарий Стивена Клири в другом вопросе о том, что Task.Run() всегда Task.Run() пул streamов даже асинхронные методы, это заставило меня подумать.

В .NET 4.5 в ASP.NET или в любом другом контексте синхронизации, который планирует задачи для текущего streamа / того же streamа, если у меня есть asynchronous метод:

 private async Task MyAsyncMethod() { ... } 

И я хочу назвать это синхронным методом, могу ли я просто использовать Task.Run() с Wait() чтобы избежать взаимоблокировок, поскольку он ставит asynchronous метод в пул streamов?

 private void MySynchronousMethodLikeDisposeForExample() { // MyAsyncMethod will get queued to the thread pool // so it shouldn't deadlock with the Wait() ?? Task.Run((Func)MyAsyncMethod).Wait(); } 

Кажется, вы понимаете риски, связанные с вашим вопросом, поэтому я пропущу лекцию.

Чтобы ответить на ваш реальный вопрос: да, вы можете просто использовать Task.Run для разгрузки этой работы в stream ThreadPool который не имеет SynchronizationContext и поэтому нет реального риска для тупика.

Тем не менее , использование другого streamа только потому, что у него нет SC, является чем-то вроде взлома и может быть дорогостоящим с момента планирования того, что работа, которая должна быть выполнена на ThreadPool имеет свои затраты.

Лучшим и ясным решением IMO было бы просто удалить SC на время, используя SynchronizationContext.SetSynchronizationContext и восстановить его впоследствии. Это можно легко инкапсулировать в IDisposable чтобы вы могли использовать его в области using :

 public static class NoSynchronizationContextScope { public static Disposable Enter() { var context = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(null); return new Disposable(context); } public struct Disposable : IDisposable { private readonly SynchronizationContext _synchronizationContext; public Disposable(SynchronizationContext synchronizationContext) { _synchronizationContext = synchronizationContext; } public void Dispose() => SynchronizationContext.SetSynchronizationContext(_synchronizationContext); } } 

Применение:

 private void MySynchronousMethodLikeDisposeForExample() { using (NoSynchronizationContextScope.Enter()) { MyAsyncMethod().Wait(); } } 

Этот код не будет тупик по тем причинам, которые вы выделили в вопросительном коде, всегда работает без контекста синхронизации (с использованием пула streamов), а Wait просто блокирует stream до / если метод возвращает.

Это мой способ избежать тупиковой ситуации, когда я должен синхронно вызывать метод асинхронного streamа, а stream может быть streamом пользовательского интерфейса:

  public static T GetResultSafe(this Task task) { if (SynchronizationContext.Current == null) return task.Result; if (task.IsCompleted) return task.Result; var tcs = new TaskCompletionSource(); task.ContinueWith(t => { var ex = t.Exception; if (ex != null) tcs.SetException(ex); else tcs.SetResult(t.Result); }, TaskScheduler.Default); return tcs.Task.Result; } 

Если вы абсолютно должны вызывать метод async из синхронного, обязательно используйте ConfigureAwait(false) внутри вызовов метода async, чтобы избежать захвата контекста синхронизации.

Это должно держаться, но в лучшем случае шатко. Я бы посоветовал подумать о рефакторинге. вместо.

При использовании небольшого пользовательского контекста синхронизации функция синхронизации может ждать завершения функции async без создания взаимоблокировки. Исходный stream сохраняется, поэтому метод синхронизации использует один и тот же stream до и после вызова функции async. Вот небольшой пример для приложения WinForms.

 Imports System.Threading Imports System.Runtime.CompilerServices Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load SyncMethod() End Sub ' waiting inside Sync method for finishing async method Public Sub SyncMethod() Dim sc As New SC sc.WaitForTask(AsyncMethod()) sc.Release() End Sub Public Async Function AsyncMethod() As Task(Of Boolean) Await Task.Delay(1000) Return True End Function End Class Public Class SC Inherits SynchronizationContext Dim OldContext As SynchronizationContext Dim ContextThread As Thread Sub New() OldContext = SynchronizationContext.Current ContextThread = Thread.CurrentThread SynchronizationContext.SetSynchronizationContext(Me) End Sub Dim DataAcquired As New Object Dim WorkWaitingCount As Long = 0 Dim ExtProc As SendOrPostCallback Dim ExtProcArg As Object  Public Overrides Sub Post(d As SendOrPostCallback, state As Object) Interlocked.Increment(WorkWaitingCount) Monitor.Enter(DataAcquired) ExtProc = d ExtProcArg = state AwakeThread() Monitor.Wait(DataAcquired) Monitor.Exit(DataAcquired) End Sub Dim ThreadSleep As Long = 0 Private Sub AwakeThread() If Interlocked.Read(ThreadSleep) > 0 Then ContextThread.Resume() End Sub Public Sub WaitForTask(Tsk As Task) Dim aw = Tsk.GetAwaiter If aw.IsCompleted Then Exit Sub While Interlocked.Read(WorkWaitingCount) > 0 Or aw.IsCompleted = False If Interlocked.Read(WorkWaitingCount) = 0 Then Interlocked.Increment(ThreadSleep) ContextThread.Suspend() Interlocked.Decrement(ThreadSleep) Else Interlocked.Decrement(WorkWaitingCount) Monitor.Enter(DataAcquired) Dim Proc = ExtProc Dim ProcArg = ExtProcArg Monitor.Pulse(DataAcquired) Monitor.Exit(DataAcquired) Proc(ProcArg) End If End While End Sub Public Sub Release() SynchronizationContext.SetSynchronizationContext(OldContext) End Sub End Class 
  • ConfigureAwait подталкивает продолжение в stream пула
  • WaitAll vs WhenAll
  • Каков правильный способ отмены асинхронной операции, которая не принимает CancellationToken?
  • Разница между TPL и async / await (Обработка streamов)
  • Почему это асинхронное действие зависает?
  • Секвенирование и реорганизация задач
  • Шаблон для самоотдачи и перезапуска задачи
  • Interesting Posts

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

    Условное форматирование Excel 2010 – ВРЕМЯ

    Как вызвать пакетный файл, который находится на одном уровне выше текущего каталога?

    Ограничение высоты ListView на Android

    Случайно удалил папку «Загрузки», окно «Специальная папка», как восстановить?

    Хорошее качество видео по сравнению с VNC или RDP?

    Не удается запустить службу W3SVC на компьютере. '

    Мое текущее местоположение всегда возвращает null. Как я могу это исправить?

    Последовательность изображений для видеоstreamа?

    Использовать AdBlock без обнаружения

    Что использует дисковое пространство?

    Отображение сетевого диска с внешнего внешнего жесткого диска

    Как получить информацию о видеофайлах из командной строки под Linux

    Android Studio – невозможно открыть файл PNG

    Восстановление пароля Outlook .pst?

    Давайте будем гением компьютера.