Async.StartImmediate与Async.RunSynchronously

app*_*umb 14 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中的任何地方记录.

Dax*_*ohl 10

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
Run Code Online (Sandbox Code Playgroud)

这将确保在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
Run Code Online (Sandbox Code Playgroud)

还要注意,Async.StartImmediate不能返回一个值(因为它在继续之前没有完成运行),而RunSynchronously可以.

  • 很好的解释,但错了.`StartImmediate`仅确保在当前线程上启动异步计算.很快你就会有`do!`或`let!`,async可以切换到任何其他线程.在GUI编程中,您需要使用`StartImmediate`启动异步获取当前上下文`let context = System.Threading.SynchronizationContext.Current`,稍后您需要使用`do显式切换回GUI线程!Async.SwitchToContext(context)`在GUI上做一些更新. (4认同)
  • @DaxFohl 行为取决于当前同步上下文的配置方式。重要的是要理解,通常 WPF 等框架会设置上下文。如果未设置上下文,它的行为就像我所描述的那样。因此,它是否在 GUI 线程上执行实际上取决于您使用的 GUI 框架。或者一般来说,如果有任何框架设置了上下文。否则,在没有任何上下文的情况下,它的行为就像我所描述的那样。 (3认同)
  • @DavidRaab http://tomasp.net/blog/async-csharp-differences.aspx/ 的最后一部分似乎证实了我的立场。`StartImmediate` 执行工作流*同时保留同步上下文*。授予任何`做!f`或`让!x = f` *可以*做一些事情来明确地屏蔽你的上下文,但在我看来,如果 `f` 的函数名称没有完全清楚地表明这一点,那么这就是 `f` 中的一个错误。在多年使用 GUI 和 F# Async 的过程中,我从未遇到过这种情况。 (2认同)