Winforms 构建可取消的非阻塞后台工作程序

Winforms 构建可取消的非阻塞后台工作程序,winforms,f#,f#-async,Winforms,F#,F# Async,假设我有一个长时间运行的计算,我想在后台运行,以免阻塞UI线程。所以我将把它包装在一个异步计算中 /// Some long-running calculation... let calculation arg = async{ do! Async.Sleep 1000 return arg } 接下来,我需要在一个循环中运行计算,在这个循环中,我切换到另一个线程来执行它,然后返回到UI线程来处理它的结果 /// Execute calculation repeatedly i

假设我有一个长时间运行的计算,我想在后台运行,以免阻塞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() }
这似乎是一个有点努力的东西,我想象作为一个相对常见的工作流程。我们能简化它吗?我遗漏了什么重要的东西吗

然后,必须将该循环包装在另一个异步计算中,以提供从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() }
我们能简化它吗

我认为cancelEvent和CancelableWorker在这种情况下是不必要的间接寻址。您可以使用CancellationTokenSource并直接从UI事件取消它,而不是反过来取消令牌的事件

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()

另外,如果您还没有查看。

感谢您展示直接从UI取消是一种可行的选择。但我担心它会涉及一些混乱的可变状态,以防我想重新启动后台循环,因为CancellationTokenSource无法重用。这就是为什么我从Tomas Petricek的另一个有价值的贡献中获得了基于事件的取消。