NETFramework中的函数式编程技术.doc_第1页
NETFramework中的函数式编程技术.doc_第2页
NETFramework中的函数式编程技术.doc_第3页
NETFramework中的函数式编程技术.doc_第4页
NETFramework中的函数式编程技术.doc_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

NET Framework 中的函数式编程技术作者:懒人ABC发表于2010-09-07 00:20原文链接阅读:2评论:2 F#入门使用.NET Framework中的函数式编程技术Ted Neward本文讨论:安装F#F#语言基础.NET互操作性异步F#本文使用了以下技术:.NET Framework,F#目录为什么要使用F#?安装F#您好,F#Let表达式关键字For管道F#也能够处理对象异步F#与F#合作作为Microsoft.NET Framework家族的新成员,F#提供类型安全、性能以及类似脚本语言的工作能力,所有这些都是.NET环境的一部分。此函数式语言由Microsoft研究院的Don Syme发明,作为CLR的OCaml语法兼容变体,但F#已经迅速地从科研转为投入实际应用。随着函数式编程的概念通过.NET泛型和LINQ等技术越来越多地渗入主流语言(例如C#和Visual Basic),F#在.NET社区里的知名程度也不断提高-因此,2007年11月Microsoft宣布将F#确定为受支持的.NET编程语言。多年来,大家一直认为函数式语言领域(ML、Haskell等)更适合用于学术研究,而不适用于专业开发。但这并不代表这些语言没有过人之处。事实上,.NET的一些重要的功能增强(例如泛型、LINQ、PLINQ和Futures)都是将一些函数式编程概念全新应用到语言所致。以往对这些语言的关注程度不高主要是因为它们的目标平台与专为Windows编写程序的开发人员关系不大、不能与底层平台很好集成,或者不支持关系数据库访问、XML解析和进程外通信机制等主要功能。但是,CLR及其多种语言,单一平台的方法将使此类语言在Windows开发中的应用越来越广泛。并且顺理成章地引起在一线工作的程序员们的注意。F#即是这样一门语言。在本文中,我将为您介绍一些F#的基本概念和优点。然后,为了帮助您初步了解F#,我将详细介绍它的安装过程并编写几个简单的小程序。为什么要使用F#?对于小部分.NET程序员来说,学习一门.NET Framework函数化语言无疑将使自己在编写功能强大软件方面前进一大步。而对其他程序员来说,学习F#的理由就因人而异了。F#能为开发人员提供哪些益处?随着多核CPU的普及,安全并发程序已成为过去三年来的关注焦点。函数式语言倡导一种固定不变的数据结构,可在线程和机器之间传递,而无需担心线程安全或原子访问,开发人员可以利用这一特点支持并发操作。函数式语言还可更轻松地编写更支持并发特性的库,如稍后将在本文中介绍的F#异步工作流。尽管对于专攻面向对象开发的程序员而言,可能对这种语言感觉不是这么强烈,但在很多情况下,函数式程序确实可以简化某些应用程序的编写和维护。例如,编写一个将XML文档转换成其他格式数据的程序。虽然完全可以通过编写一个C#程序,让它解析整个XML文档并应用各种if语句确定在文档中的不同位置采取何种措施,但实际上更好的方法是编写可扩展样式表语言转换(XSLT)程序。当然,XSLT肯定包含大量的内置函数机制,如同SQL一样。F#强烈建议不要使用空值(null),而是提倡使用固定不变的数据结构。这些特性可以减少需要编写的特例代码量,从而有助于降低编程出错的频率。使用F#编写的程序还更加简洁。您可以切实地从两方面减少键入的内容:击键次数更少并且必须要向编译器通告变量类型、参数或返回类型的位置点也更少。这意味着需要维护的代码将大大减少。F#具有与C#相似的性能特点。但是,与简洁程度相似的语言(特别是那些动态和脚本语言)相比,它的性能特点要好得多。并且,F#也包含通过编写程序段并交互式执行查看数据的工具,这一点与许多动态语言类似。安装F#F#可从/fsharp/fsharp.aspx免费下载,它不仅会安装所有命令行工具,而且还会安装Visual Studio扩展软件包,该软件包提供彩色语法突出显示、项目和文件模板(包括作为入门指南的详细F#示例代码)以及IntelliSense支持。同时它还提供可在Visual Studio内部运行的F#交互式shell,它使开发人员能够从源文件窗口中提取表达式、将表达式粘贴到交互式shell窗口,并立即得到代码段的结果-在类似增强的Immediate窗口中显示结果。在我撰写本专栏时,F#在Visual Studio内作为外部工具运行,所以它缺少某些开发人员能够从C#或Visual Basic中获得的无缝集成的能力。此外,F#还缺少ASP.NET页面设计器支持。(这并不是说F#不能在ASP.NET中使用,完全不是。这仅表示Visual Studio并没有为F#提供类似C#和Visual Basic的那种现成拖放式开发体验。)尽管如此,当前版本的F#还是可以在能够使用其他.NET兼容语言的任何位置使用。在接下来的几页中,您将看到一些示例。您好,F#介绍任何语言的特有方式就是通过那几乎成为标准的Hello,World程序。F#也不例外:复制代码printfHello,world!虽然不能引起您太大的兴趣,但这个很小的例子显示出F#属于不需要显式入口点(C#、Visual Basic和C+/CLI都需要显式入口点)的语言;该语言假设程序的第一行即为入口点并将从此处开始执行。要运行此程序,刚入门的F#开发人员有两个选择:编译或解释。在F#解释程序中运行此程序(fsi.exe)很简单。只需从命令行启动fsi.exe并在出现的提示中输入上面一行内容即可,如图1所示。图1在F#解释程序中运行Hello,World(单击该图像获得较大视图)请注意,在shell中,每条语句必须以两个分号结尾。这是交互模式的特殊要求,编译过的F#程序并不需要使用这种方式。要将此示例作为标准的.NET可执行程序运行,请正常启动Visual Studio并创建一个新的F#项目(可以在其他项目类型中找到这个项目)。最初,F#项目包含一个F#源文件,称为file1.fs。打开此文件将发现大量示例F#代码集合。浏览一下这些内容即可大概了解F#的语法结构。然后将整个文件替换成前面所显示的Hello,world!代码。运行此应用程序,毫无疑问,在控制台应用程序窗口中将出现Hello,world!。如果更喜欢命令行方式,可以使用F#安装目录中bin子目录里的fsc.exe工具编译这段代码。请注意:fsc.exe与大多数命令行编译器的工作方式相似,它从命令行获取源代码并生成可执行程序作为结果。大多数命令行开关都有相关文档,如果曾经使用过csc.exe或cl.exe编译器,那您可能已经熟悉其中许多开关。不过请注意,F#目前对MSBuild的支持尚不完美;在当前安装版本(撰写本文时为)中不能直接支持由MSBuild驱动的编译。如果希望Hello,world!中更具图形化特点,F#可以通过CLR平台(包括Windows Forms库)轻松地提供完整的逼真度和互操作性。尝试以下代码:复制代码System.Windows.Forms.MessageBox.ShowHello World利用.NET Framework类库和F#库的能力使得F#语言不仅对那些早已使用如OCaml或Haskell之类函数式语言进行数学和科学计算的社区极具吸引力,而且受到全世界现有.NET开发人员的青睐。Let表达式让我们看看比传统Hello,world!更复杂一些的F#代码示例。请看以下代码:复制代码let results=for iin 0.100-(i,i*i)printfnresults=%Aresults在该F#语法中,let表达式是令人产生好奇的元素。它是整个语言中最重要的表达式。更正式地说,let可以为标识符赋值。用Visual Basic和C#开发人员的行话来说,它可以定义一个变量。但这并不确切。在F#中,标识符包含两个要素。首先,标识符一旦定义就不能再更改。(这即是F#帮助程序员创建并发安全程序的方法,因为它不提倡可变的状态。)第二,标识符不仅可以是基元类型或对象类型(如C#和Visual Basic中所使用的类型),而且还可以是函数类型,这一点与LINQ相似。同时还请注意,标识符从不显式定义为类型。例如,从不定义结果标识符;它将从后面表达式的右侧进行推断。这称为类型推断,并且它代表编译器分析代码、确定返回值和自动插入返回值的能力(这与新的C#推断类型表达式通过变量关键字进行推断的方法类似)。Let表达式不仅可以处理数据,还可以使用它来定义函数,F#将函数看作第一级概念。下面的示例定义了一个加法函数,它使用两个参数a和b:复制代码let add ab=a+b完全按照您预期的方式工作:将a与b相加,并将结果显式返回给调用者。这意味着从技术上讲,F#中的每个函数都将返回一个值,即使返回的不一定是值,也会返回一个特殊的名称unit。这将在F#代码中产生一些有趣的暗示,特别是与.NET Framework类库相交的部分。但目前,C#和Visual Basic开发人员可以把unit大致看作是与void相同的类型。有时函数应该忽略传递给它的参数。要在F#中达此目的,仅需使用下划线作为该参数的占位符即可:复制代码let return10 _=add 55/12 is effectively ignored,and ten is set to the resulting/value of add 55 let ten=return10 12 printften=%dnten与许多函数式语言类似,F#允许根据其调用进行currying(可以仅部分定义函数的应用),以便提供其余的参数:复制代码let add5 a=add a5在某种程度上,这与创建一个接受不同的参数集并调用其他方法的重载方法相似:复制代码public class Adderspublic static int add(int a,int b)return a+b;public static int add5(int a)return add(a,5);但两者还是有一点细微的差别。请注意,在F#版本中,没有显式定义类型。这表示编译器将采用自己的类型推断方法确定add5的参数是否是与加上整数5兼容的类型,并确定是按照这种方式编译,还是将其标记为错误。事实上,F#语言主要使用隐式类型参数化(即使用泛型)。在Visual Studio中,将指针停放在前面所显示的ten的定义上时,将表明其类型声明为:复制代码val ten:(a-int)在F#中,这表示ten是一个值,一个获取任意类型的参数并产生整数结果的函数。这种记号语法大致等同于C#中的T语法,所以对C#函数最贴切的说法是:ten是类型参数化方法的委托实例,您想要忽略其类型(但在C#规则下不可忽略):复制代码delegate int Transformer T(T ignored);public class Apppublic static int return10(object ignored)return 5+5;static void Main()Transformer object ten=return10;System.Console.WriteLine(ten=0,return10(0);关键字For现在让我们看一下第一个示例中的for关键字:复制代码#light let results=for iin 0.100-(i,i*i)printfnresults=%Aresults先看代码的顶部,注意#light语法。这是为非OCaml程序员开始使用F#而做的让步,放宽某些OCaml语言的语法要求,并使用大量空白定义代码块。虽然不一定必要,但对于那些原本使用C#或Visual Basic的普通开发人员来说,它确实使语法更易于解析,因此它常出现在F#示例和公开的代码段中,并已成为事实上的F#编程标准。(未来版本的F#可能会将#light确定为默认语法,代替其他类似方法。)那个看似简单的for循环实际上并不简单。正式地说,这是生成列表,即将生成列表型结果的代码块的另一种说法。列表是函数式语言中经常出现的一种原语结构,在这方面它与数组有许多相似之处。但是,列表不允许基于位置的访问(如C#中传统的ai语法)。列表可在函数式编程的不同位置出现,大多数情况中,可以将其看作是F#中与.NET Framework中的List T类似,但提供一些增强功能的同等项。列表通常使用一些特殊类型,本例中标识符结果是聚合列表,特别地是F#将此聚合类型标识为类型(int*int)。如果将此聚合列表看作是SQL中SELECT语句返回的一对列,它的含义就清楚多了。因此,示例本质上是创建一个包含100个项目的整数对列表。通常,在函数式语言中,函数定义可以在出现代码的任何位置使用。因此,如果希望扩展前面的示例,可以编写以下代码:复制代码let compute2 x=(x,x*x)let compute3 x=(x,x*x,x*x*x)let results2=for iin 0.100-compute2 ilet results3=for iin 0.100-compute3 i遍历列表(或数组或其他一些重复结构)是函数式语言中很常见的任务,它已成为基本方法调用:List.iter。它仅简单地对列表中的每个元素调用一个函数。其他类似的库函数还可提供一些非常有用的功能。例如,List.map将函数作为参数,并将该函数应用于列表中的每个元素,并返回该过程产生的新列表。管道让我们讨论F#中另一个结构-管道操作符,它通过类似命令shell(如Windows PowerShell)管道的通道获取函数的结果,并将结果用作后一个函数的输入。我们来看图2中显示的F#代码段。该代码段使用System.Net命名空间连接HTTP服务器,获取相应的HTML并分析结果。Figure 2检索和分析HTML复制代码/Get the contents of the URL via aweb request let http(url:string)=let req=System.Net.WebRequest.Create(url)let resp=req.GetResponse()let stream=resp.GetResponseStream()let reader=new System.IO.StreamReader(stream)let html=reader.ReadToEnd()resp.Close()html let getWords s=String.split;n;t;=s let getStats site=let url=+site let html=http url let words=html|getWords let hrefs=html|getWords|List.filter(fun s-s=href)(site,html.Length,words.Length,hrefs.Length)请注意getStats定义中的words标识符。它获取从URL返回的html值,并对其应用getWords函数。我还可以编写定义读取:复制代码let words=getWords html两者等同。但是hrefs标识符显示了管道操作符的威力,通过管道操作符可以将任意多个应用程序连接起来。此处我获取words的结果列表,并将其通过管道传递给List.filter函数,该函数使用匿名函数查找单词href,并在表达式为true时将其返回。并且,最重要的是,getStats调用的结果将是另一个聚合(string*int*int*int)。要使用C#编写,需要的远远不止15行代码。图2中的示例还显示出更多F#与.NET Framework的兼容性,以下代码也表现出这一特性:复制代码open System.Collections.Generic let capitals=Dictionary string,string()capitals.Great Britain-Londoncapitals.France-Pariscapitals.ContainsKey(France)确实,这个示例除了练习Dictionary K,V类型外没有什么其他内容,但它显示出在F#中如何指定泛型(使用与C#相同的尖括号)、如何在F#中使用索引(同样与C#一样使用方括号),以及如何执行.NET方法(使用与C#中同样的点和圆括号)。事实上,这里仅有的新内容是使用左箭头操作符为可变值赋值。这一点是必需的,因为F#与大多数函数式语言一样,保留等号用于比较,以便保持数学符号含义:如果x=y,则x与y的值相等,而不是将y的值赋给x。(真正的数学家们早已对普遍存在或设想过的x=x+1提出异议,甚至偷笑不已。)F#也能够处理对象当然,并不是所有开始使用.NET的开发人员都愿意立即接受函数式的概念。事实上,大多数从C#或Visual Basic转向F#的开发人员都需要知道他们在使用这一新语言时可以保留原有的习惯。在某种程度上,这是完全可行的。例如,请看图3顶部所示的二维向量的类定义。其中就有一些有趣的概念。首先,请注意其中没有显式构造函数体;第一行中的参数表明用于构造Vector2D实例的参数本质上就是构造函数。因此长度标识符,以及dx和dy标识符将成为Vector2D类型的私有元素,而member关键字则表明可以通过标准.NET属性访问获取的Vector2D外部可用成员。本质上,这段F#代码声明了您可在图3底部看到的内容(由Reflector报告)。Figure 3F#和C#中的矢量变体复制代码VECTOR2D IN F#type Vector2D(dx:float,dy:float)=let length=sqrt(dx*dx+dy*dy)member obj.Length=length member obj.DX=dx member obj.DY=dy member obj.Move(dx2,dy2)=Vector2D(dx+dx2,dy+dy2)VECTOR2D IN C#(REFLECTORSerializable,CompilationMapping(SourceLevelConstruct.ObjectType)public class Vector2D/Fields internal double _dx48;internal double _dy48;internal double _length49;/Methods public Vector2D(double dx,double dy)Hello.Vector2Dthis=this;this._dx48=dx;this._dy48=dy;double d=(this._dx48*this._dx48)+(this._dy48*this._dy48);this._length49=Math.Sqrt(d);public Hello.Vector2D Move(double dx2,double dy2)return new Hello.Vector2D(this._dx48+dx2,this._dy48+dy2);/Properties public double DXgetreturn this._dx48;public double DYgetreturn this._dy48;public double Lengthgetreturn this._length49;请记住,F#与大多数函数式语言相似,提倡使用不变的值和状态。当查看图3中的代码时,这一点尤为明显,因为所有属性都为只读属性,并且Move成员不会修改现有的Vector2D,而是从当前Vector2D创建新的副本,并在返回副本之前对其应用修改的值。还请注意,F#版本不仅具备整体线程安全性,而且完全可以通过传统的C#或Visual Basic代码进行访问。这为F#入门提供了一种简便方法:使用它定义想要或者需要线程安全和固定不变的业务对象或其他类型。虽然完全可以在F#中创建提供常用可变操作组(设置属性及类似操作)的类型,但需要更多的工作,而且需要使用mutable关键字才可完成。在当今并发问题成为日常工作主旋律的世界中,这正如许多人所要求的一样-默认固定不变,必需或想要时可变。在F#中创建类型很有趣,但还是可以用F#去做那些传统C#或Visual Basic代码可以做到的工作,如创建简单的Windows窗体应用程序并从用户处收集输入,如图4所示。Figure 4使用F#编写Windows窗体复制代码#light open System open System.IO open System.Windows.Forms open Printf let form=new Form(Text=My First F#Form,Visible=true)let menu=form.Menu-new MainMenu()let mnuFile=form.Menu.MenuItems.Add(&File)let filter=txt files(*.txt)|*.txt|All files(*.*)|*.*let mnuiOpen=new MenuItem(&Open.,new EventHandler(fun _-let dialog=new OpenFileDialog(InitialDirectory=c:,Filter=filter;FilterIndex=2,RestoreDirectory=true)if dialog.ShowDialog()=DialogResult.OK then match dialog.OpenFile()with|null-printfCould not read the file.n|s-let r=new StreamReader(s)printfFirst line is:%s!n(r.ReadLine();s.Close();),Shortcut.CtrlO)mnuFile.MenuItems.Add(mnuiOpen)STAThreaddo Application.Run(form)任何熟悉Windows窗体的开发人员都能够立即明白这些代码的含义:创建一个简单的窗体、填充一些属性、填入一个事件处理程序,并告诉应用程序开始运行,直到用户单击右上角的关闭按钮。由于标准元素与.NET应用程序相同,所以只需重点关注F#语法即可。Open语句的操作与C#中using语句的作用大致相同,本质上都是打开.NET命名空间以便在没有正式限制符的情况下使用。Printf命名空间是F#原有的、技术上与OCaml模块具有相同名称的端口。F#不仅具备完整的.NET Framework类库,而且还有最简洁的OCaml库端口,这使得熟悉该语言的程序员能够象使用.NET Framework一样对其运用自如。(致好奇心强的程序员:Printf位于FSharp.Core.dll程序集中。)您完全可以根据个人偏好随时使用System.Console.WriteLine。窗体标识符的创建利用了F#命名参数,它等同于实例化对象,然后调用一系列属性集来为这些属性填充值。我在下面的几行中使用相同的方法创建对话框标识符。mnuiOpen标识符的定义包含令人感兴趣的结构,该结构对于熟悉.NET Framework 2.0匿名委托或.NET Framework 3.5中lambda表达式的开发人员来说并不陌生。构造与Open MenuItem关联的EventHandler时,您可以看到使用以下语法定义的匿名函数:复制代码fun _-.类似于匿名委托,这段代码创建了一个将会在选中菜单项时调用的函数,但语法略有技巧性。MenuItem定义中对EventHandler的定义是忽略传递给它的两个参数的匿名函数,这两个参数巧妙地对应标准EventHandler委托类型中的发送方和事件参数。该函数规定显示新的OpenFileDialog并在单击确定时检查结果.如下所示:复制代码if dialog.ShowDialog()=DialogResult.OK then match dialog.OpenFile()with|null-printfCould not read the file.n|s-let r=new StreamReader(s)in printfFirst line is:%s!n(r.ReadLine();s.Close();将使用模式匹配检查结果,该方法是函数化语言世界中一项强大的功能。模式匹配表面上与C#中的switch/case在某些地方存在相似之处,但实际上它名副其实地完成模式匹配工作:它将值与各种不同的模式进行比较(这些模式不需要都是常量值),并执行匹配的代码块。因此,以此处所示的匹配块为例,OpenFile的结果可以匹配两种可能的值:null表示无法打开任何文件,或者分配任何非null值的s,该值将随后用作StreamReader的构造函数来打开并读取给定文本文件的第一行。模式匹配是大多数函数式语言的重要部分,对它做些研究是完全值得的。它的一个最常见的用途是与可辨识联合(discriminated union)类型(C#或Visual Basic中枚举类型的不确切说法)配合使用:复制代码/Declaration of theExprtype type Expr=|Binary of string*Expr*Expr|Variable of string|Constant of int/Create avaluevrepresentingx+10let v=Binary(+,Variablex,Constant 10)函数式语言中常用它来创建域特定语言的核心表示,开发人员可以使用它来编写更为复杂和强大的结构。例如,不难想象扩展此语法以创建完全计算式语言,并可简单地通过为Expr类型添加新元素而进一步扩展该语言。这里需要注意的是:使用*字符的语法并不表示使用乘法,它是函数式语言中用于指示某类型中包含多个部分的标准方式。事实上,函数式语言已经非常普遍地应用于编写面向语言的编程工具(如解释器和编译器),并且Expr类型最终将成为语言表达式类型的完整集合。F#通过其内置的两个工具:fslex和fsyacc(专为获得传统语言输入-lex和yacc文件-并将其编译成F#代码以便简化操作而设计)使这一切变得更为简单。如果对此感兴趣,可以下载F#安装程序深入研究;特别是标准F#发行包中的Parsing示例将提供非常好的入门基础结构。可辨识联合只是模式匹配的优势之一,另一项优势是表达式的执行,如图5所示。位于eval定义中的rec是必需的,它告诉F#编译器在定义主体内迭代调用eval。如果没有它,F#将期望出现一个名为eval的本地嵌套函数。实际使用时,我使用函数getVarValue为变量返回一些预定义的值,getVarValue将检查Dictionary,查找变量创建时确定的返回值。Figure 5表达式执行复制代码let getVarValue v=match vwith|x-25|y-12|_-0 let rec eval x=match xwith|Binary(op,l,r)-let(lv,rv)=(eval l,eval r)in if(op=+)then lv+rv elif(op=-)then lv-rv else failwithE_UNSUPPORTED|Variable(var)-getVarValue var|Constant(n)-n do printfResults=%dn(eval v)当调用eval时,它将得到值v并发现该值是一个Binary值。这与第一个子表达式匹配,该表达式随后把值(lv,rv)绑定到刚检查的Binary值左右两侧的计算结果。未命名的值(lv,rv)是一个聚合(本质上是代表多个部分的单个值),这一点与关系集或C结构相似。当首次调用eval l时,来自Binary实例的l恰好是Variable类型,因此对eval的递归调用匹配该模式匹配块的分支。随后将调用getVarValue,它会返回硬编码25,该值最终将绑定到值lv。对于包含值10的常量r来说顺序相同,因此它将绑定到rv。然后执行代码块的剩余部分(if/else-if/else块),熟悉C#、Visual Basic或C+的开发人员可以很容易地读懂该代码块的含义。这里需要再次强调的是:每个表达式都将返回一个值,甚至在模式匹配块内部也一样。在本例中,返回值是一个整型值,该值可能是运算得到的值、从变量中检索到的值或者是常量本身。这一点似乎更容易让习惯于面向对象或过程化编程的开发人员产生微词,因为在C#、Visual Basic或C+中,返回值是可选的,并且甚至在指定返回值的情况下仍可以忽略返回值。在类似F#的函数式语言中,要忽略返回值需要显式编码表达方式。如果出现这种情况,程序员可以将结果传给名为ignore的函数,由它完成适当的操作。异步F#目前为止,我对F#语法的介绍采用以下两种方式中的一种:或者使用相对简单的函数式结构,或者使其看起来比较初级且简洁,象是传统面向对象、.NET兼容语言(C#、Visual Basic或C+/CLI)的变体。这种介绍很难推动在企业内采用F#。但是请看一下图6。它可与前面两种形式截然不同。除了多处出现!字符并使用async修饰符外,这是一段看起来相对比较直观的代码:加载源图像映像、提取其数据、将数据传递到独立的函数进行加工(旋转、拉伸或其他操作),并将数据写回输出文件。Figure 6处理图像复制代码let TransformImage pixels i=/Some kind of graphic manipulation of images let ProcessImage(i)=asyncuse inStream=File.OpenRead(sprintfsource%d.jpgi)let!pixels=inStream.ReadAsync(1024*1024)let pixels=TransformImage(pixels,i)use outStream=File.OpenWrite(sprintfresult%d.jpgi)do!outStream.WriteAsync(pixels)do Console.WriteLinedone!let ProcessImages()=Async.Run(Async.Parallelfor iin 1.numImages-ProcessImage(i)较不明显的是使用async修饰符使这段代码进入F#所称的异步工作流(与Windows Workflow Foundation无关)中,这意味着这些加载/处理/保存步骤的每一步都在.NET线程池的平行线程中执行。为了使其更简单,看一下图7中的代码。这种特殊的顺序以相对简单且易于理解的方式显示出异步工作流。不用深究细节,我们就可以看出evals是一组待执行的函数,通过Async.Parallel调用使其中每个函数都在线程池中排队等待执行。当执行时,可以看出实际上evals中的函数与awr中的函数在不同的线程中(尽管由于.NET系统线程池的特性,部分或全部evals函数有可能在相同的线程中执行)。Figure 7异步执行函数复制代码#light open System.Threading let printWithThread str=printfnThreadId=%d%sThread.CurrentThread.ManagedThreadId str let evals=let z=4.0asyncdo printWithThreadComputing z*znreturn z*z;asyncdo printWithThreadComputing sin(z)n

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论