Использовать Task.Run () в синхронном методе, чтобы избежать тупиковой остановки в асинхронном методе?
ОБНОВЛЕНИЕ objective этого вопроса – получить простой ответ о Task.Run()
и тупиковой ситуации. Я очень понимаю теоретические рассуждения о том, что вы не смешиваете асинхронные и синхронизирующие сигналы, и я принимаю их близко к сердцу. Я не выше изучения новых вещей от других; Я стараюсь делать это, когда смогу. Есть только времена, когда всем нужен парень, это технический ответ …
У меня есть метод Dispose()
который должен вызвать метод async. Поскольку 95% моего кода является асинхронным, рефакторинг не лучший выбор. Наличие IAsyncDisposable
(среди других функций), которое поддерживается каркасом, было бы идеальным, но мы пока не находимся. Поэтому, в то же время, мне нужно найти надежный способ вызова асинхронных методов из синхронного метода без взаимоблокировки.
Я бы предпочел не использовать ConfigureAwait(false)
потому что это оставляет ответственность, разбросанную по всему моему коду, чтобы вызываемый мог вести себя определенным образом, только если вызывающий является синхронным. Я бы предпочел что-то сделать в синхронном методе, так как это девиантный bugger.
- Должны ли мы переключиться на использование асинхронного ввода-вывода по умолчанию?
- Запуск нескольких задач async и ожидание их завершения
- Отмена ожидающей задачи синхронно в streamе пользовательского интерфейса
- Вызов синхронного асинхронного метода
- Выполнение задачи в фоновом режиме в приложении WPF
Прочитав комментарий Стивена Клири в другом вопросе о том, что 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(); }
- Запуск двух асинхронных задач параллельно и сбор результатов в .NET 4.5
- Когда использовать Task.Delay, когда использовать Thread.Sleep?
- Как ограничить количество одновременных операций асинхронного ввода-вывода?
- Выполнение задач параллельно
- Запуск задач в foreach Loop использует значение последнего элемента
- Очередь процесса с многопоточным или задачами
- В чем разница между возвратом пустоты и возвратом задачи?
- Лучший способ конвертировать метод async на основе обратного вызова в ожидаемую задачу
Кажется, вы понимаете риски, связанные с вашим вопросом, поэтому я пропущу лекцию.
Чтобы ответить на ваш реальный вопрос: да, вы можете просто использовать 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