在取消不同类型的Asyncs时,我遇到了看似不一致的行为问题。
为了重现这个问题,让我们说有一个功能可以列出一些"作业" (Async< _>列表),等待它们完成并打印其结果。该函数还会获取取消令牌,因此可以取消它:
let processJobs jobs cancel =
Async.Start(async {
try
let! results = jobs |> Async.Parallel
printfn "%A" results
finally
printfn "stopped"
}, cancel)
该函数的调用方式如下:
let jobs = [job1(); job2(); job3(); job4(); job5()]
use cancel = new CancellationTokenSource()
processJobs jobs cancel.Token
稍后它会被取消:
Thread.Sleep(1000)
printfn "cancelling..."
cancel.Cancel()
取消取消令牌来源时,该功能应执行finally块并打印"已停止"。
适用于job1,2和3,但如果列表中有job4或job5,则无法正常工作。
Job1 只是Async.Sleeps:
let job1() = async {
do! Async.Sleep 1000000
return 10
}
Job2 启动一些异步子项并等待它们:
let job2() = async {
let! child1 = Async.StartChild(async {
do! Async.Sleep 1000000
return 10
})
let! child2 = Async.StartChild(async {
do! Async.Sleep 1000000
return 10
})
let! results = [child1; child2] |> Async.Parallel
return results |> Seq.sum
}
Job3 等待一些丑陋的等待句柄,这些句柄由一些更丑陋的线程设置:
let job3() = async {
use doneevent = new ManualResetEvent(false)
let thread = Thread(fun () -> Thread.Sleep(1000000); doneevent.Set() |> ignore)
thread.Start()
do! Async.AwaitWaitHandle(doneevent :> WaitHandle) |> Async.Ignore
return 30
}
Job4 发帖并等待来自MailboxProcessor的回复:
let job4() = async {
let worker = MailboxProcessor.Start(fun inbox -> async {
let! (msg:AsyncReplyChannel<int>) = inbox.Receive()
do! Async.Sleep 1000000
msg.Reply 40
})
return! worker.PostAndAsyncReply (fun reply -> reply) // <- cannot cancel this
}
Job5 等待任务(或TaskCompletionSource):
let job5() = async {
let tcs = TaskCompletionSource<int>()
Async.Start(async {
do! Async.Sleep 1000000
tcs.SetResult 50
})
return! (Async.AwaitTask tcs.Task) // <- cannot cancel this
}
为什么Job1,2和3会被取消(&#34;停止&#34;被打印),而Job4和5使该功能挂起&#34;永远&#34;? < / p>
到目前为止,我总是依靠F#来处理幕后取消 - 只要我在async-blocks中使用!s(让!,do!,return!,...)一切都应该是很好..但似乎并非一直如此。
在F#异步工作流中,传递CancellationToken对象 在封面下自动左右。这意味着我们不必这样做 做一些特别的事情来支持取消。运行异步时 工作流程,我们可以给它取消令牌,一切都会工作 自动。
此处提供完整代码:http://codepad.org/euVO3xgP
修改
我注意到通过Async.StartAsTask和Async.AwaitTask管道异步使得它在所有情况下都可以取消。
即。对于Job4,这意味着更改行:
return! worker.PostAndAsyncReply (fun reply -> reply)
为:
return! cancelable <| worker.PostAndAsyncReply (fun reply -> reply)
可取消的是:
let cancelable (x:Async<_>) = async {
let! cancel = Async.CancellationToken
return! Async.StartAsTask(x, cancellationToken = cancel) |> Async.AwaitTask
}
同样可以使Job5取消。
但是......这只是一种解决方法,我几乎无法在每次调用时将其置于未知的Async&lt; _&gt ;.
答案 0 :(得分:1)
只有异步。方法使用默认的CancellationToken本身处理。
在您的MailboxProcessor示例中,取消应该在Start方法
上let! ct= Async.CancellationToken
use worker := MailboxProcessor.Start( theWork, ct)
在TaskCompletionSource示例中,您将必须注册一个回调来取消它。
let! ct = Async.CancellationToken
use canceler = ct.Register( fun () -> tcs.TrySetCanceled() |> ignore )