asynchronous - Async.TryCancelled 不适用于 Async.RunSynchronously

标签 asynchronous f# agent mailboxprocessor

我尝试创建一个根据用户交互更新 UI 的代理。如果用户单击按钮,则应刷新 GUI。模型的准备需要很长时间,因此希望如果用户单击其他按钮,则取消准备并开始新的准备。

到目前为止我所拥有的:

open System.Threading
type private RefreshMsg = 
    | RefreshMsg of AsyncReplyChannel<CancellationTokenSource>

type RefresherAgent() =  
    let mutable cancel : CancellationTokenSource = null

    let doSomeModelComputation i =
        async { 
          printfn "start %A" i
          do! Async.Sleep(1000)
          printfn "middle %A" i
          do! Async.Sleep(1000)
          printfn "end %A" i
        }
    let mbox = 
        MailboxProcessor.Start(fun mbx ->
            let rec loop () = async {
                let! msg = mbx.Receive()
                match msg with
                | RefreshMsg(chnl) ->
                    let cancelSrc = new CancellationTokenSource()
                    chnl.Reply(cancelSrc)
                    let update = async {
                                    do! doSomeModelComputation 1
                                    do! doSomeModelComputation 2
                                    //do! updateUI // not important now
                                 }
                    let cupdate = Async.TryCancelled(update, (fun c -> printfn "refresh cancelled"))
                    Async.RunSynchronously(cupdate, -1, cancelSrc.Token)
                    printfn "loop()"
                    return! loop()
            }
            loop ())
    do
        mbox.Error.Add(fun exn -> printfn "Error in refresher: %A" exn)
    member x.Refresh() = 
        if cancel <> null then
            // I don't handle whether the previous computation finished
            // I just cancel it; might be improved
            cancel.Cancel()
            cancel.Dispose()
        cancel <- mbox.PostAndReply(fun reply -> RefreshMsg(reply))
        printfn "x.Refresh end"

//sample  
let agent = RefresherAgent()
agent.Refresh()
System.Threading.Thread.Sleep(1500)
agent.Refresh()

我为每个请求返回一个 CancellationTokenSource 并将其存储在一个可变变量中(x.Refresh() 是线程安全的,它在 UI 线程上调用)。 如果第一次调用 Refresh(),则返回取消源。如果第二次调用 Refresh(),我会调用 Cancel,这会中止我通过 Async.RunSynchronously 运行的异步任务。

但是,会引发异常。我的示例的输出是

x.Refresh end
start 1
middle 1
end 1
refresh cancelled
Error in refresher: System.OperationCanceledException: The operation was canceled.
   at Microsoft.FSharp.Control.AsyncBuilderImpl.commit[a](Result`1 res)

现在当我想到这一点时,这可能是有道理的,因为代理运行的线程被中断了,对吧?但是,我如何实现所需的行为?


我需要取消代理内部的异步工作流程,以便代理可以继续使用新消息。为什么要使用邮箱处理器?因为保证只有一个线程尝试创建 UI 模型,所以节省了资源。

假设我通过从多个 Web 服务下载数据来创建 UI 模型,这就是我使用异步调用的原因。当用户更改组合并选择其他选项时,我想停止使用旧值查询 Web 服务(= 取消异步调用),并希望使用新值创建新的模型基础 od Web 服务调用。

也欢迎任何可以代替我的解决方案并解决我的问题的建议。

最佳答案

我很难理解你想要实现的目标。但也许这并不重要 - 错误只是说您正在使用 RunSynchronously 执行的工作流程已被取消(RunSynchronously 将抛出异常) - 因此您可以将此调用包装到 try-match block 中,然后忽略 OC 异常

更好的选择可能是重构你的 cupdate 和其中的 try-match - 如果你直接捕获 OC-Exceptions,你甚至可以将 TryCancelled 引入其中;)

let update = 
   async {
      try
         do! doSomeModelComputation 1
         do! doSomeModelComputation 2
      with
      | :? OperationCanceledException -> 
           printfn "refresh cancelled"
   }
Async.RunSynchronously(update, -1, cancelSrc.Token)

但我仍然不明白为什么你想要同步

关于asynchronous - Async.TryCancelled 不适用于 Async.RunSynchronously,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9601299/

相关文章:

javascript - AngularJS 双向数据绑定(bind)被 setTimeout 取消

F# 将字符串传递给列表

固件-IDAS : Issue with registered device

jenkins - 管道中的 channel 关闭异常

ios - 如何等待异步 NSURLConnection 响应

javascript - 如何将异步状态传递给子组件 Prop ?

c# - 异步在异步 Controller mvc 4.0 中不起作用

parsing - 在 FParsec 中使用带有标识符解析器的预处理函数?

interface - F# 接口(interface)和属性

facebook - 作为移动设备的 OAuth Facebook 页面(useragent 和 cordova)