本文共 7318 字,大约阅读时间需要 24 分钟。
本文翻译自:
Preface : I'm looking for an explanation, not just a solution. 前言 :我在寻找一个解释,而不仅仅是一个解决方案。 I already know the solution. 我已经知道了解决方案。
Despite having spent several days studying MSDN articles about the Task-based Asynchronous Pattern (TAP), async and await, I'm still a bit confused about some of the finer details. 尽管花了几天时间研究有关基于任务的异步模式(TAP),异步和等待的MSDN文章,但我对某些更详细的信息仍然感到困惑。
I'm writing a logger for Windows Store Apps, and I want to support both asynchronous and synchronous logging. 我正在为Windows Store Apps编写记录器,并且希望同时支持异步和同步记录。 The asynchronous methods follow the TAP, the synchronous ones should hide all this, and look and work like ordinary methods. 异步方法遵循TAP,同步方法应该隐藏所有这些内容,并且外观和工作方式与普通方法类似。
This is the core method of asynchronous logging: 这是异步日志记录的核心方法:
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);}
Now the corresponding synchronous method... 现在对应的同步方法...
Version 1 : 版本1 :
private void WriteToLog(string text){ Task task = WriteToLogAsync(text); task.Wait();}
This looks correct, but it does not work. 看起来正确,但是不起作用。 The whole program freezes forever. 整个程序永久冻结。
Version 2 : 版本2 :
Hmm.. Maybe the task was not started? 嗯..也许任务没有开始?
private void WriteToLog(string text){ Task task = WriteToLogAsync(text); task.Start(); task.Wait();}
This throws InvalidOperationException: Start may not be called on a promise-style task.
这将引发InvalidOperationException: Start may not be called on a promise-style task.
Version 3: 版本3:
Hmm.. Task.RunSynchronously
sounds promising. 嗯Task.RunSynchronously
听起来很有希望。
private void WriteToLog(string text){ Task task = WriteToLogAsync(text); task.RunSynchronously();}
This throws InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
这将引发InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Version 4 (the solution): 版本4(解决方案):
private void WriteToLog(string text){ var task = Task.Run(async () => { await WriteToLogAsync(text); }); task.Wait();}
This works. 这可行。 So, 2 and 3 are the wrong tools. 因此,2和3是错误的工具。 But 1? 但是1? What's wrong with 1 and what's the difference to 4? 1有什么问题,与4有什么区别? What makes 1 cause a freeze? 是什么导致1冻结? Is there some problem with the task object? 任务对象有问题吗? Is there a non-obvious deadlock? 有没有明显的僵局?
参考:
The await
inside your asynchronous method is trying to come back to the UI thread. 异步方法中的await
正在尝试返回UI线程。
Since the UI thread is busy waiting for the entire task to complete, you have a deadlock. 由于UI线程正忙于等待整个任务完成,因此出现了死锁。
Moving the async call to Task.Run()
solves the issue. 将异步调用移到Task.Run()
可以解决此问题。
Alternatively, you could call StartAsTask().ConfigureAwait(false)
before awaiting the inner operation to make it come back to the thread pool rather than the UI thread, avoiding the deadlock entirely. 或者,您可以在等待内部操作使它返回线程池而不是UI线程之前调用StartAsTask().ConfigureAwait(false)
,从而完全避免死锁。
Calling async
code from synchronous code can be quite tricky. 从同步代码中调用async
代码可能非常棘手。
I explain the . 我解释了的 。 In short, there's a "context" that is saved by default at the beginning of each await
and used to resume the method. 简而言之,有一个“上下文”默认情况下在每次await
的开始时保存,并用于恢复该方法。
So if this is called in an UI context, when the await
completes, the async
method tries to re-enter that context to continue executing. 因此,如果在UI上下文中调用此方法,则在await
完成时, async
方法将尝试重新输入该上下文以继续执行。 Unfortunately, code using Wait
(or Result
) will block a thread in that context, so the async
method cannot complete. 不幸的是,使用Wait
(或Result
)的代码将在该上下文中阻塞线程,因此async
方法无法完成。
The guidelines to avoid this are: 避免这种情况的准则是:
ConfigureAwait(continueOnCapturedContext: false)
as much as possible. 尽可能使用ConfigureAwait(continueOnCapturedContext: false)
。 This enables your async
methods to continue executing without having to re-enter the context. 这使您的async
方法可以继续执行而不必重新输入上下文。 async
all the way. 一路使用async
。 Use await
instead of Result
or Wait
. 使用await
代替Result
或Wait
。 If your method is naturally asynchronous, then . 如果您的方法自然是异步的,那么 。
With small custom synchronization context, sync function can wait for completion of async function, without creating deadlock. 使用小的自定义同步上下文,同步功能可以等待异步功能完成,而不会产生死锁。 Here is small example for WinForms app. 这是WinForms应用程序的一个小示例。
Imports System.ThreadingImports System.Runtime.CompilerServicesPublic 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 FunctionEnd ClassPublic 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 ObjectPublic 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 SubEnd Class
Here is what I did 这是我所做的
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();}
working great and not blocking UI thread 工作正常,不阻塞UI线程
转载地址:http://ygcnb.baihongyu.com/