探索F#的异步工作流

在本文中,将探索F#语言的强大功能之一——异步工作流。异步工作流允许在不阻塞用户界面(UI)的情况下执行计算或工作。将通过一个简单的例子来演示这一点:在UI中弹跳多个椭圆对象。

首先,需要了解F#的基础知识。本文假设已经具备了2010年最新版本的软件,并且熟悉F#和WPF的基本概念。

目标

目标并不宏伟,只是想展示F#异步工作流的一个简单应用。将创建一个响应式UI,其中包含多个在UI中弹跳的椭圆对象,而不会阻塞UI。

让开始吧...

将创建一个名为MainWindow.xaml的XAML文件。以下是简单的XAML代码:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="使用F#的乐趣... :)" Height="450" Width="500" WindowState="Maximized"> <Canvas Name="canvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" /> </Window>

如所见,有一个主窗口和一个画布(Canvas)。使用画布的原因是可以精确地定位子控件在画布区域内的任何位置。

现在,将为项目添加一个.fs文件来编写一些F#代码。将其命名为SomeFunThisTime.fs。在文件顶部,将打开(导入)一些命名空间:

open System open System.Windows open System.Windows.Controls open System.Windows.Shapes

接下来,定义一个运算符,它将帮助在XAML中定义的窗口子控件集合中按名称查找控件。这是在根级别进行的。

let (?) (fe:FrameworkElement) name : 'T = fe.FindName(name) :?> 'T

还将定义一些将在代码中使用的常量值。让在模块下创建它们:

module CONST = let ELLIPSE_WIDTH = 20.0 let ELLIPSE_HEIGHT = 20.0

现在已经完成了根级别所需的辅助代码。接下来,将创建应用程序的入口点。将通过以下代码实现这一点:

[<STAThread>] [<EntryPoint>] let main(_) = (new Application()).Run(mainWindow)

目前,请不要担心mainWindow作为上述代码中的参数。假设它在代码的某个地方已经定义好了。接下来将定义它...

确保代码的缩进正确。在F#中,缩进对编译器很重要。以下是mainWindow函数的定义:

let mainWindow = let startupWin = Application.LoadComponent(new System.Uri("/App;component/mainwindow.xaml", UriKind.Relative)) :?> Window SomeFunThisTime.MyEllipse(startupWin) |> ignore startupWin

上述代码加载了mainwindow.xaml文件,并将其转换为Window对象并返回。如果将鼠标悬停在mainWindow上,IDE将显示返回类型为"Window"。到目前为止,一切都很直接。在上述代码中,有一行代码如下所示:

SomeFunThisTime.MyEllipse(startupWin) |> ignore

让现在实现它。

将创建一个名为SomeFunThisTime的模块。在该模块下,将创建一个名为MyEllipse的类,其构造函数有一个Window类型的参数。

在MyEllipse类中,将有两个函数和一些初始化代码。以下是完整的代码。不会详细解释每一行代码,但会强调一些重要的代码行。

module SomeFunThisTime = type MyEllipse(win : Window) = let winObj = win let elp width height = let obj : Ellipse = new Ellipse() obj.Width <- width obj.Height <- height obj.Fill <- Media.Brushes.Red obj.Stroke <- Media.Brushes.Black obj.StrokeThickness <- 5.0 obj let startFalling (elp : Ellipse) = let rnd = new Random() async { while true do for x = 0 to 400 do do! Async.Sleep(rnd.Next(0, 3)) Canvas.SetTop(elp, float x) for x = 400 downto 0 do do! Async.Sleep(rnd.Next(0, 3)) Canvas.SetTop(elp, float x) } |> Async.StartImmediate do winObj.Loaded.Add(fun _ -> let canvas : Canvas = winObj?canvas let rnd = new Random() [ 0..45 ] |> Seq.map(fun x -> elp CONST.ELLIPSE_WIDTH CONST.ELLIPSE_HEIGHT) |> Seq.iteri(fun i e -> async { do! Async.Sleep(i * rnd.Next(0, 500)) canvas.Children.Add(e) |> ignore System.Windows.Controls.Canvas.SetTop(e, 0.0) System.Windows.Controls.Canvas.SetLeft(e, float (i * (int CONST.ELLIPSE_WIDTH))) e.Loaded.Add(fun _ -> startFalling(e)) } |> Async.StartImmediate ) ) member this.Window = winObj

如果注意到了,在上面的代码中,注册了一个Window Loaded事件。在该事件中,以异步模式创建了0..45的椭圆对象。通过以下代码引入了延迟:

do! Async.Sleep(i * rnd.Next(0, 500))

这行代码将线程返回给线程池,延迟时间为其Sleep方法提供的毫秒数。这将创建一个随机创建椭圆的效果,而不是按顺序创建。下面的代码还将确保将椭圆相邻放置,而不是相互覆盖。

另一行有趣的代码在startFalling下。它无限循环地异步地将椭圆从顶部弹跳到底部,然后再弹跳回顶部。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485