假设我有一个长时间运行的计算,我想在后台运行,因为不阻止UI线程。所以我将它包装在一个异步计算中。
/// Some long-running calculation...
let calculation arg = async{
do! Async.Sleep 1000
return arg }
接下来我需要在一个循环中运行计算,在这个循环中我切换到另一个线程来执行它并返回到UI线程以对其结果做一些事情。
/// Execute calculation repeatedly in for-loop and
/// display results on UI thread after every step
open System.Threading
let backgroundLoop uiAction = async {
let ctx = SynchronizationContext.Current
for arg in 0..100 do
do! Async.SwitchToThreadPool()
let! result = calculation arg
do! Async.SwitchToContext ctx
uiAction result }
然后,必须将此循环包装在另一个异步计算中,以提供一些从UI中取消它的方法。
/// Event-controlled cancellation wrapper
let cancelEvent = new Event<_>()
let cancellableWorker work = async {
use cToken = new CancellationTokenSource()
Async.StartImmediate(work, cToken.Token)
do! Async.AwaitEvent cancelEvent.Publish
cToken.Cancel() }
现在看来,我已经实现了类似于BackgroundWorker
的功能。测试它:
// Demo where the results are added to a ListBox.
// The background calculations can be stopped
// by a keypress when the ListBox has focus
open System.Windows.Forms
let fm = new Form()
let lb = new ListBox(Dock = DockStyle.Fill)
fm.Controls.Add lb
fm.Load.Add <| fun _ ->
backgroundLoop (lb.Items.Add >> ignore)
|> cancellableWorker
|> Async.StartImmediate
lb.KeyDown.Add <| fun _ ->
cancelEvent.Trigger()
[<System.STAThread>]
#if INTERACTIVE
fm.Show()
#else
Application.Run fm
#endif
对于我认为相对常见的工作流程来说,似乎需要付出一些努力。我们可以简化它吗?我错过了什么至关重要的事情吗?
答案 0 :(得分:4)
然后,必须将此循环包装在另一个异步计算中,以提供一些从UI中取消它的方法。
我们可以简化吗?
我认为cancelEvent
和cancellableWorker
在这种情况下是不必要的间接。您可以使用CancellationTokenSource
直接从UI事件取消它,而不是取消令牌的Event<>
。
let calculation arg = async {
do! Async.Sleep 1000
return arg }
open System.Threading
let backgroundLoop uiAction = async {
let ctx = SynchronizationContext.Current
for arg in 0..100 do
do! Async.SwitchToThreadPool()
let! result = calculation arg
do! Async.SwitchToContext ctx
uiAction result }
open System.Windows.Forms
let fm = new Form()
let lb = new ListBox(Dock = DockStyle.Fill)
fm.Controls.Add lb
let cToken = new CancellationTokenSource()
fm.Load.Add <| fun _ ->
Async.StartImmediate (backgroundLoop (lb.Items.Add >> ignore), cToken.Token)
lb.KeyDown.Add <| fun _ -> cToken.Cancel()
另外(如果你还没有)看看Tomas Petricek's article on non-blocking user-interfaces in F#。