同步等待asynchronous操作,为什么Wait()在这里冻结程序
前言 :我正在寻找一个解释,而不仅仅是一个解决scheme。 我已经知道解决scheme。
尽pipe花了几天的时间研究有关基于任务的asynchronous模式(TAP)的asynchronous和等待的MSDN文章,但我仍然对某些更精细的细节感到困惑。
我正在为Windowsapp store应用程序编写logging器,并且要支持asynchronous和同步logging。 asynchronous方法遵循TAP,同步的方法应该隐藏所有这些,并且像普通的方法一样工作。
这是asynchronouslogging的核心方法:
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(解决scheme):
private void WriteToLog(string text) { var task = Task.Run(async () => { await WriteToLogAsync(text); }); task.Wait(); }
这工作。 所以,2和3是错误的工具。 但是1? 1和4有什么不同? 是什么让1导致冻结? 任务对象有问题吗? 有没有明显的死锁?
请帮我理解。
你的asynchronous方法中的await
正在尝试返回到UI线程。
由于UI线程忙于等待整个任务完成,所以会出现死锁。
将asynchronous调用移动到Task.Run()
可解决此问题。
因为asynchronous调用现在正在线程池线程上运行,所以不会尝试返回到UI线程,因此所有工作都可以正常工作。
或者,您可以在等待内部操作之前调用StartAsTask().ConfigureAwait(false)
,使其返回到线程池而不是UI线程,从而完全避免死锁。
见http://ermau.com/avoiding-callbacks-to-the-ui-thread-with-async-and-winrt/
从同步代码调用async
代码可能非常棘手。
我在博客上解释了这个僵局的全部原因 。 简而言之,在每个await
开始时默认保存一个“上下文”,并用于恢复该方法。
因此,如果在UI上下文中调用此方法,则在await
完成时, async
方法会尝试重新input该上下文以继续执行。 不幸的是,使用Wait
(或Result
)的代码将会阻塞该上下文中的线程,所以async
方法无法完成。
避免这种情况的指导原则是:
- 尽可能使用
ConfigureAwait(continueOnCapturedContext: false)
。 这使您的async
方法可以继续执行,而无需重新input上下文。 - 一路使用
async
。 使用await
而不是Result
或Wait
。
如果你的方法是自然的asynchronous的,那么你(可能)不应该公开一个同步包装 。
这是我做的
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线程
对于小的自定义同步上下文,sync函数可以等待asynchronous函数的完成,而不会造成死锁。 这里是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