Async.StartImmediate против Async.RunSynchronously
Как мое ограниченное (или даже неправильное) понимание, как Async.StartImmediate, так и Async.RunSynchronously запускают асинхронное вычисление на текущем потоке. Тогда в чем же разница между этими двумя функциями? Может кто-нибудь помочь объяснить?
Update:
Изучив исходный код F # на https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/control.fs, я думаю, что я понимаю, что происходит. Async.StartImmediate запускает асинхронный поток текущего потока. После того, как он ударит асинхронную привязку, будет ли она продолжать работать в текущем потоке, зависит от самой привязки async. Например, если асинхронная привязка вызывает Async.SwitchToThreadPool, она будет работать на ThreadPool вместо текущего потока. В этом случае вам нужно будет вызвать Async.SwitchToContext, если вы хотите вернуться к текущему потоку. В противном случае, если привязка async не делает никакого переключения на другие потоки, Async.StartImmediate будет продолжать выполнять асинхронную привязку к текущему потоку. В этом случае нет необходимости вызывать Async.SwitchToContext, если вы просто хотите остаться в текущем потоке.
Причина, по которой пример Dax Fohls работает в потоке графического интерфейса, заключается в том, что 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 не является нулевым, что всегда имеет место для потока GUI. Особенно,
если вы запустите консольное приложение с StartImmediate, вы обнаружите, что Async.Sleep действительно отправится в ThreadPool, потому что основной поток в консольном приложении не имеет SynchronizationContext.Current.
Итак, чтобы подвести итог, это действительно работает с потоком GUI, потому что некоторые функции, такие как Async.Sleep, Async.AwaitWaitHandle и т.д., тщательно захватывают и не забудьте вернуться к предыдущему контексту.
Похоже, это преднамеренное поведение, однако это, похоже, не документировано нигде в MSDN.
Ответы
Ответ 1
Async.RunSynchronously
ожидает завершения всего вычисления. Поэтому используйте это в любое время, когда вам нужно выполнить асинхронное вычисление из обычного кода и ждать результата. Достаточно просто.
Async.StartImmediate
гарантирует, что вычисление выполняется в текущем контексте, но не дожидается завершения всего выражения. Наиболее частое использование этого (для меня, по крайней мере) - это то, когда вы хотите асинхронно запускать вычисление в потоке графического интерфейса. Например, если вы хотите сделать три вещи в потоке графического интерфейса с интервалом в 1 секунду, вы можете написать
async {
do! Async.Sleep 1000
doThing1()
do! Async.Sleep 1000
doThing2()
do! Async.Sleep 1000
doThing3()
} |> Async.StartImmediate
Это обеспечит, чтобы все вызывалось в потоке GUI (при условии, что вы вызываете это из потока GUI), но не будет блокировать поток GUI в течение целых 3 секунд. Если вы используете RunSynchronously
там, он будет блокировать поток 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
может.