Async.StartImmediate与Async.RunSynchronously

时间:2016-09-17 12:38:20

标签: f#

由于我的理解有限(甚至是错误),Async.StartImmediate和Async.RunSynchronously都会在当前线程上启动异步计算。那么这两个函数之间究竟有什么区别呢?任何人都可以帮忙解释一下吗?

更新

https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/control.fs查看F#源代码后,我想我有点理解会发生什么。 Async.StartImmediate在当前线程上启动异步。在遇到异步绑定后,它是否会继续在当前线程上运行取决于异步绑定本身。例如,如果异步绑定调用Async.SwitchToThreadPool,它将在ThreadPool而不是当前线程上运行。在这种情况下,如果要返回当前线程,则需要调用Async.SwitchToContext。否则,如果异步绑定没有切换到其他线程,Async.StartImmediate将继续在当前线程上执行异步绑定。在这种情况下,如果您只想保留当前线程,则无需调用Async.SwitchToContext。

Dax Fohl的示例在GUI线程上运行的原因是因为Async.Sleep仔细捕获 SynchronizationContext.Current并确保继续在捕获的上下文中运行 SynchronizationContext.Post()。请参阅https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/control.fs#L1631,其中unprotectedPrimitiveWithResync包装器更改“args.cont”(续) 成为捕获上下文的帖子(参见:https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/control.fs#L1008 - trampolineHolder.Post基本上是SynchronizationContext.Post)。这只会起作用 当SynchronizationContext.Current不为null时,GUI线程总是如此。特别, 如果您使用StartImmediate在控制台应用程序中运行,您会发现Async.Sleep确实会转到ThreadPool,因为控制台应用程序中的主线程没有SynchronizationContext.Current。

总而言之,这确实适用于GUI线程,因为Async.Sleep,Async.AwaitWaitHandle等某些功能会仔细捕获并确保回发到上一个上下文。 它看起来是一种故意的行为,但是这似乎没有记录在MSDN的任何地方。

1 个答案:

答案 0 :(得分:9)

Async.RunSynchronously等待整个计算完成。因此,只要您需要从常规代码运行异步计算并需要等待结果,就可以使用它。很简单。

Async.StartImmediate确保计算在当前上下文中运行,但不等到整个表达式完成。最常见的用途(对我来说,至少)是你想要在GUI线程上异步运行计算。例如,如果您想以1秒的间隔在GUI线程上执行三项操作,则可以编写

async {
  do! Async.Sleep 1000
  doThing1()
  do! Async.Sleep 1000
  doThing2()
  do! Async.Sleep 1000
  doThing3()
} |> Async.StartImmediate

这将确保在GUI线程中调用所有内容(假设您从 GUI线程调用),但不会在整个3秒内阻止GUI线程。如果你在那里使用RunSynchronously,它将在一段时间内阻止GUI线程,你的屏幕将无法响应。

(如果你还没有完成GUI编程,那么请注意,GUI控件的更新都必须从同一个线程完成,这可能很难手动协调;上面带来了很多痛苦)。

再举一个例子,这里:

// Async.StartImmediate
async {
  printfn "Running"
  do! Async.Sleep 1000
  printfn "Finished"
} |> Async.StartImmediate
printfn "Next"

> Running
> Next
// 1 sec later
> Finished

// Async.RunSynchronously
async {
  printfn "Running"
  do! Async.Sleep 1000
  printfn "Finished"
} |> Async.RunSynchronously
printfn "Next"

> Running
// 1 sec later
> Finished
> Next

// Async.Start just for completion:
async {
  printfn "Running"
  do! Async.Sleep 1000
  printfn "Finished"
} |> Async.Start
printfn "Next"

> Next
> Running // With possible race condition since they're two different threads.
// 1 sec later
> Finished

另请注意,Async.StartImmediate无法返回值(因为它在继续之前未完成运行),而RunSynchronously可以。