前言 :我在寻找一个解释,而不仅仅是一个解决方案。 我已经知道了解决方案。
尽管已经花了几天时间研究有关基于任务的异步模式(TAP),异步MSDN文章和等待,我还是有点困惑的某些细节。
我正在写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引起冻结? 有一些问题与任务对象? 是否有一个不明显的僵局?
该await
你的异步方法内试图回到UI线程。
由于UI线程繁忙等待整个任务的完成,你有一个僵局。
移动异步调用Task.Run()
解决了这个问题。
由于异步调用现在在一个线程池线程运行时,它并不试图回来到UI线程,因此,一切正常。
或者,你可以调用StartAsTask().ConfigureAwait(false)
,等待内部操作,使其回到线程池,而不是UI线程,完全避免僵局之前。
调用async
从同步码代码可以是相当棘手。
我解释了这个僵局在我的博客充分的理由 。 总之,有一个“上下文”,即在默认情况下,在每个月初保存await
和用于恢复的方法。
所以,如果这就是所谓的在UI方面,当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();
}
伟大的工作,而不是阻塞UI线程
随着小自定义同步情况下,同步功能可以等待异步函数的完成,而不产生死锁。 下面是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
文章来源: Synchronously waiting for an async operation, and why does Wait() freeze the program here