Синхронно ожидание асинхронной операции и почему Wait () замораживает программу здесь
предисловие: Я ищу объяснение, а не просто решение. Я уже знаю решение.
несмотря на то, что я провел несколько дней, изучая статьи MSDN об асинхронном шаблоне на основе задач (TAP), async и await, я все еще немного смущен некоторыми более тонкими деталями.
Я пишу регистратор для приложений Магазина Windows, и я хочу поддерживать как асинхронное, так и синхронное ведение журнала. Асинхронные методы следуют за TAP, синхронные должны скрывать все это, выглядеть и работать как обычные методы.
это основной метод асинхронного ведения журнала:
private async Task WriteToLogAsync(string text)
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.CreateFileAsync("log.log",
CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, text,
Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
теперь синхронный метод...
Вариант 1:
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
это выглядит правильно, но это не работает. Вся программа замерзает навсегда.
Вариант 2:
Мда.. Возможно, задача была не в этом. начал?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
это бросает InvalidOperationException: Start may not be called on a promise-style task.
Вариант 3:
Мда.. Task.RunSynchronously
звучит многообещающе.
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.RunSynchronously();
}
это бросает InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Вариант 4 (решение):
private void WriteToLog(string text)
{
var task = Task.Run(async () => { await WriteToLogAsync(text); });
task.Wait();
}
это работает. Итак, 2 и 3-неправильные инструменты. Но 1? Что не так с 1 и в чем разница с 4? Что делает 1 причиной заморозки? Есть ли какие-то проблемы с объектом task? Есть неочевидные взаимоблокировки?
пожалуйста, помогите мне понять.
4 ответов
на await
внутри асинхронного метода пытается вернуться в поток пользовательского интерфейса.
поскольку поток пользовательского интерфейса занят ожиданием завершения всей задачи, у вас есть взаимоблокировка.
перемещение асинхронного вызова в Task.Run()
решает эту проблему.
Поскольку асинхронный вызов теперь выполняется в потоке пула потоков, он не пытается вернуться в поток пользовательского интерфейса, и поэтому все работает.
кроме того, вы можете позвонить StartAsTask().ConfigureAwait(false)
перед ожиданием внутренняя операция, чтобы заставить его вернуться в пул потоков, а не поток пользовательского интерфейса, полностью избегая взаимоблокировки.
вызов async
код из синхронного кода может быть довольно сложно.
объясняю:полные причины этого тупика в моем блоге. Короче говоря, есть "контекст", который сохраняется по умолчанию в начале каждого await
и используется для возобновления методом.
поэтому, если это вызывается в контексте пользовательского интерфейса, когда await
завершается,async
метод пытается повторно ввести этот контекст для продолжения выполнения. К сожалению, код с помощью Wait
(или Result
) заблокирует поток в этом контексте, поэтому async
метод не может быть завершен.
рекомендации, чтобы избежать этого:
- использовать
ConfigureAwait(continueOnCapturedContext: false)
как можно больше. Это позволяет вашasync
методы для продолжения выполнения без повторного входа в контекст. - использовать
async
всю дорогу. Использоватьawait
вместоResult
илиWait
.
если ваш метод естественно асинхронный, то вы (вероятно) не должны выставить синхронную обертку.
вот что я сделал
private void myEvent_Handler(object sender, SomeEvent e)
{
// I dont know how many times this event will fire
Task t = new Task(() =>
{
if (something == true)
{
DoSomething(e);
}
});
t.RunSynchronously();
}
работает и не блокирует поток пользовательского интерфейса
С небольшой пользовательский контекст синхронизации, функция синхронизации может ждать завершения асинхронной функции, не создавая затор. Вот небольшой пример для приложения 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