如何在F#中编写异步
WPF(或
Windows窗体)事件处理程序?具体来说,是否有任何编码模式接近C#5的异步并等待?
这是一个完整的C#WPF应用程序:
using System; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; class Program { static int IncrementSlowly(int prevIoUs) { Thread.Sleep(3000); if (prevIoUs == 2) throw new Exception("Oops!"); return prevIoUs + 1; } static async void btn_Click(object sender,RoutedEventArgs e) { var btn = sender as Button; btn.IsEnabled = false; try { var prev = (int)btn.Content; btn.Content = await Task.Run(() => IncrementSlowly(prev)); } catch (Exception ex) { btn.Content = ex.Message; } finally { btn.IsEnabled = true; } } [STAThread] static void Main(string[] args) { var btn = new Button() { Content = 0 }; var win = new Window() { Content = btn }; btn.Click += btn_Click; new Application().Run(win); } }
我无法弄清楚使用F#的等价物.我使用异步工作流和异步方法的组合进行了多次尝试.它真的很快就真的很乱.我希望有一个简单的方法,我只是俯瞰.
这是我的起点,它将UI锁定在btn.Content< - incrementSlowly prev.接下来我该怎么办?
open System open System.Threading open System.Threading.Tasks open System.Windows open System.Windows.Controls let incrementSlowly prevIoUs = Thread.Sleep(3000) if prevIoUs = 2 then failwith "Oops!" prevIoUs + 1 let btn_Click (sender : obj) e = let btn = sender :?> Button btn.IsEnabled <- false try try let prev = btn.Content :?> int btn.Content <- incrementSlowly prev with ex -> btn.Content <- ex.Message finally btn.IsEnabled <- true [<EntryPoint>][<STAThread>] let main _ = let btn = new Button(Content = 0) let win = new Window(Content = btn) btn.Click.AddHandler(RoutedEventHandler(btn_Click)) Application().Run(win)
顺便说一下,假设incrementSlowly不能被修改.
解决方法
第一步是使incrementSlowly异步.这在C#代码中实际上是同步的,这可能不是一个好主意 – 在实际场景中,这可能是与网络通信,因此通常这实际上可以是异步的:
let incrementSlowly prevIoUs = async { do! Async.Sleep(3000) if prevIoUs = 2 then failwith "Oops!" return prevIoUs + 1 }
现在,您可以使按钮单击处理程序也异步.我们稍后将使用Async.StartImmediate启动它以确保我们可以访问UI元素,因此我们现在不必担心调度程序或UI线程:
let btn_Click (sender : obj) e = async { let btn = sender :?> Button btn.IsEnabled <- false try try let prev = btn.Content :?> int let! next = incrementSlowly prev btn.Content <- next with ex -> btn.Content <- ex.Message finally btn.IsEnabled <- true }
最后一步是更改事件注册.像这样的东西应该做的伎俩:
btn.Click.Add(RoutedEventHandler(fun sender e -> btn_Click sender e |> Async.StartImmediate)
关键是Async.StartImmediate启动异步工作流程.当我们在UI线程上调用它时,它确保所有实际工作都在UI线程上完成(除非您明确地将其卸载到后台),因此可以安全地访问代码中的UI元素.