博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
同步等待异步操作,为什么Wait()在这里冻结程序
阅读量:2290 次
发布时间:2019-05-09

本文共 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? 有没有明显的僵局?


#1楼

参考:


#2楼

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()可以解决此问题。

Because the async call is now running on a thread pool thread, it doesn't try to come back to the UI thread, and everything therefore works. 由于异步调用现在正在线程池线程上运行,因此它不会尝试返回UI线程,因此一切正常。

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) ,从而完全避免死锁。


#3楼

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: 避免这种情况的准则是:

  1. Use 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方法可以继续执行而不必重新输入上下文。
  2. Use async all the way. 一路使用async Use await instead of Result or Wait . 使用await代替ResultWait

If your method is naturally asynchronous, then . 如果您的方法自然是异步的,那么 。


#4楼

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 Object    
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 SubEnd Class

#5楼

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/

你可能感兴趣的文章
poj1753——Flip Game(枚举+dfs)
查看>>
poj2965——The Pilots Brothers' refrigerator(模拟)
查看>>
poj2506——Tiling(递推+大数加)
查看>>
poj3295——Tautology(构造法)
查看>>
poj1573——Robot Motion(模拟)
查看>>
poj2485——Highways(最小生成树+prim)
查看>>
poj3083——Children of the Candy Corn(bfs)
查看>>
poj3026——Borg Maze(BFS+最小生成树)
查看>>
poj1094——Sorting It All Out(拓扑排序)
查看>>
poj1035——Spell checker
查看>>
poj3080——Blue Jeans(字串)
查看>>
poj2299——Ultra-QuickSort(归并排序)
查看>>
poj3122——Pie(二分+贪心)
查看>>
poj1936——All in All
查看>>
poj3349——Snowflake Snow Snowflakes(hash表)
查看>>
poj2418——Hardwood Species(stl+map)
查看>>
poj2488——A Knight's Journey(dfs)
查看>>
poj3087——Shuffle'm Up(BFS)
查看>>
poj3687——Labeling Balls(拓扑排序)
查看>>
蓝桥杯算法训练——最小乘积(基本型)
查看>>