Используйте Task.Run () в синхронном методе, чтобы избежать взаимоблокировки ожидания асинхронного метода?
обновление цель этого вопроса-получить простой ответ о Task.Run()
и взаимоблокировки. Я очень хорошо понимаю теоретические рассуждения о том, чтобы не смешивать асинхронность и синхронизацию, и я принимаю их близко к сердцу. Я не против учиться новому у других; я стараюсь делать это, когда могу. Бывают моменты, когда все, что нужно парню-это технический ответ...
у меня есть Dispose()
метод, который должен вызвать метод async. Поскольку 95% моего кода является асинхронным, рефакторинг-не лучший выбор. Имея IAsyncDisposable
(среди других функций), которые поддерживаются платформой, были бы идеальными, но мы еще не там. Поэтому в то же время мне нужно найти надежный способ вызова асинхронных методов из синхронного метода без блокировки.
Я бы предпочел не использовать ConfigureAwait(false)
потому что это оставляет ответственность, разбросанную по всему моему коду для вызываемого, чтобы вести себя определенным образом на случай, если вызывающий абонент синхронен. Я бы предпочитаю делать что-то синхронным методом, так как это отклоняющийся педераст.
прочитав комментарий Стивена Клири в другом вопросе, что Task.Run()
всегда планирует в пуле потоков даже асинхронные методы, это заставило меня задуматься.
в .NET 4.5 in ASP.NET или любой другой контекст синхронизации, который планирует задачи в текущий поток / тот же поток, если у меня есть асинхронный метод:
private async Task MyAsyncMethod()
{
...
}
и я хочу вызвать его из синхронного метода, могу ли я просто используйте Task.Run()
С Wait()
чтобы избежать взаимоблокировок, так как он ставит в очередь асинхронный метод пула потоков?
private void MySynchronousMethodLikeDisposeForExample()
{
// MyAsyncMethod will get queued to the thread pool
// so it shouldn't deadlock with the Wait() ??
Task.Run((Func<Task>)MyAsyncMethod).Wait();
}
5 ответов
похоже, вы понимаете риски, связанные с вашим вопросом, поэтому я пропущу лекцию.
отвечая на ваш вопрос: Да, вы можете просто использовать Task.Run
чтобы разгрузить эту работу ThreadPool
поток, который не имеет SynchronizationContext
и поэтому нет никакого реального риска для тупика.
, используя другой поток только потому, что у него нет 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();
}
}
этот код не будет взаимоблокировки именно по причинам, которые вы выделили в вопросе-код всегда работает без контекста синхронизации (с использованием пула потоков) и Wait
просто заблокирует поток, пока / если метод не вернется.
Это мой способ избежать взаимоблокировки, когда мне нужно синхронно вызывать асинхронный метод, и поток может быть потоком UI:
public static T GetResultSafe<T>(this Task<T> task)
{
if (SynchronizationContext.Current == null)
return task.Result;
if (task.IsCompleted)
return task.Result;
var tcs = new TaskCompletionSource<T>();
task.ContinueWith(t =>
{
var ex = t.Exception;
if (ex != null)
tcs.SetException(ex);
else
tcs.SetResult(t.Result);
}, TaskScheduler.Default);
return tcs.Task.Result;
}
Если вы абсолютно должны вызовите асинхронный метод из синхронного, обязательно используйте ConfigureAwait(false)
внутри асинхронных вызовов, чтобы избежать захвата контекста синхронизации.
Это должно держаться, но в лучшем случае шатко. Я бы посоветовал подумать о рефакторинге. вместо.
С небольшой пользовательский контекст синхронизации, функция синхронизации может ждать завершения асинхронной функции, не создавая затор. Исходный поток сохраняется, поэтому метод синхронизации использует тот же поток до и после вызова функции 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
<MethodImpl(MethodImplOptions.Synchronized)>
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