在本文中,将探索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下。它无限循环地异步地将椭圆从顶部弹跳到底部,然后再弹跳回顶部。