我认为在F#异步库的标准库函数中没有直接的方法来实现这一点。最接近的操作是使用
Async.TryCancelled
,它会在工作流程被(实际)取消时运行回调函数,但是从回调函数发送消息到启动工作流的代码必须手动完成。
使用事件和我编写的 F# 异步扩展中包含的一个扩展程序(也包括在 FSharpX 包中)可以相对容易地解决这个问题——该扩展程序是
GuardedAwaitObservable
,可用于等待事件的发生(该事件可以由某些操作立即触发)。
下面的
Async.StartCancellable
方法接受一个异步工作流并返回
Async<Async<unit>>
。当您在外部工作流上绑定时,它会启动参数(如
Async.StartChild
),当您在返回的内部工作流上绑定时,它会取消计算并等待直到它实际被取消:
open System.Threading
module Async =
/// Returns an asynchronous workflow 'Async<Async<unit>>'. When called
/// using 'let!', it starts the workflow provided as an argument and returns
/// a token that can be used to cancel the started work - this is an
/// (asynchronously) blocking operation that waits until the workflow
/// is actually cancelled
let StartCancellable work = async {
let cts = new CancellationTokenSource()
// Creates an event used for notification
let evt = new Event<_>()
// Wrap the workflow with TryCancelled and notify when cancelled
Async.Start(Async.TryCancelled(work, ignore >> evt.Trigger), cts.Token)
// Return a workflow that waits for 'evt' and triggers 'Cancel'
// after it attaches the event handler (to avoid missing event occurrence)
let waitForCancel = Async.GuardedAwaitObservable evt.Publish cts.Cancel
return async.TryFinally(waitForCancel, cts.Dispose) }
编辑:按照Jon的建议,将结果包装在TryFinally
中以处理CancellationTokenSource
的释放。我认为这应该足以确保正确地进行了释放。
下面是一个使用该方法的示例。 loop
函数是我用于测试的简单工作流程。 其余的代码启动它,等待5.5秒然后取消它:
/// Sample workflow that repeatedly starts and stops long running operation
let loop = async {
for i in 0 .. 9999 do
printfn "Starting: %d" i
do! Async.Sleep(1000)
printfn "Done: %d" i }
// Start the 'loop' workflow, wait for 5.5 seconds and then
// cancel it and wait until it finishes current operation
async { let! cancelToken = Async.StartCancellable(loop)
printfn "started"
do! Async.Sleep(5500)
printfn "cancelling"
do! cancelToken
printfn "done" }
|> Async.Start
为了完整起见,来自FSharpX的必要定义示例可以在F#片段上找到。
CancellationTokenSource
吗? - J DFSharp.Core
中泄漏问题的文章,我相信这个问题是由于没有处理CTS而导致的:http://t0yv0.blogspot.com/2011/12/solving-f-asyncstartchild-leak-futures.html - t0yv0Dispose
肯定更好。我编辑了答案,在计算被取消(并且取消已完成)后,在终结器中调用Dispose
。 - Tomas PetricekAsync.TryCancelled
之外再使用async.TryFinally
包装work
)。 - Tomas Petricek