2012-03-07 4 views
2

ユーザーの操作に基づいてUIを更新するエージェントを作成しようとしています。ユーザーがボタンをクリックすると、GUIがリフレッシュされます。モデルの作成には長い時間がかかりますので、ユーザーが他のボタンをクリックすると、準備がキャンセルされ、新しいものが開始されることが望まれます。私がこれまで持って何Async.TryCancelledはAsync.RunSynchronouslyで動作しません

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

私は(x.Refresh()はスレッドセーフで、それはUIスレッド上で呼び出された)要求ごとにCancellationTokenSourceを返し、変更可能な変数に格納します。 Refresh()が初めて呼び出された場合、キャンセルソースが返されます。 Refresh()が2回目に呼び出された場合、Cancelを呼び出し、Async.RunSynchronouslyを実行する非同期タスクを中止する必要があります。

ただし、例外が発生します。私のサンプルからの出力は、私がこのことについて考えるように、エージェントが実行されるスレッドは、右、interrputedれたので、それは、意味をなすかもしれない今

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) 

のですか?しかし、どのようにして目的の動作を達成できますか?


私は、エージェントが新しいメッセージを消費し続けることができるように、エージェントの内部で非同期ワークフローをキャンセルする必要があります。メールボックスプロセッサを使用するのはなぜですか? 1つのスレッドだけがUIモデルを作成しようとしていることが保証されているので、リソースを節約します。

いくつかのWebサービスからデータをダウンロードしてUIモデルを作成するとします。そのため、非同期呼び出しを使用しています。ユーザーがコンボを変更して他のオプションを選択すると、古い値のWebサービス(=非同期呼び出しをキャンセル)のクエリを停止し、新しい値で新しいモデルベースのWebサービス呼び出しを作成したいと考えています。

私の解決策の代わりに使うことができ、私の問題を解決する提案も歓迎です。

答えて

2

達成したいことを理解しようとするのが難しいです。しかし、これは問題ではないかもしれません - エラーはちょうどRunSynchronouslyで実行しているワークフローがキャンセルされたことを示しています(RunSynchronouslyは例外をスローします) - この呼び出しをtry-matchブロックにラップしてOC-例外を無視するだけです

より良いオプションは、あなたのcupdateをリファクタリングするかもしれないと、この内側のtry-試合に - あなたは直接OC-例外をキャッチした場合、あなたもそれにTryCancelledでもたらすことができます;)

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

しかし、それでもまだ、私あなたがなぜこれを同期させたいのか分からない

+0

背景をよりよく説明する段落を追加しました。 – stej

+0

あなたはexcをキャッチすることは解決策だと思っていますが、よりよいものがあることを願っています。たぶん 'Async'の他のメソッドの組み合わせ(' TryCancelled'と 'RunSynchronously'以外)。 – stej

+0

私は私の答えに提案を追加しました...私はこれをテストすることはできませんが、私は*予想どおりに動作するはずだと思う* – Carsten

関連する問題