最近,在探索F#语言的工作流特性时,产生了一个疯狂的想法:"能否使用F#的工作流特性来构建一个类似Windows Workflow Foundation的工作流引擎呢?" 在这篇文章中,将尝试构建这样一个引擎,至少是一个非常初级的版本。首先,会为那些还没有机会接触F#及其工作流特性的读者提供一些有用的链接:
如果继续阅读这篇文章,那么可能已经具备了一些F#知识,或者已经查看了上述链接……或者只是想看看这个疯狂的想法到底是怎么回事。
让从一个简单的示例开始:
let workflowHost = new WorkflowEngine()
let fibonacciWorkflow number =
workflowHost {
do printfn "Started!"
let! f1 = fib number
let! f2 = fib (number + 1)
do printfn "Finished"
do printfn "Result is %s" ((f1 + f2).ToString())
return 0 //成功
}
runWorkflow(fibonacciWorkflow(22));;
在这里,有一个名为fibonacciWorkflow
的工作流,它非常简单:计算两个斐波那契数并打印它们的和。fib
函数用于计算给定位置的斐波那契数。老实说,是从网上随便复制粘贴的这个函数:
let fib n =
let rec fib_aux (n, a, b) =
match (n, a, b) with
| (0, a, b) -> a
| _ -> fib_aux (n - 1, a + b, a)
fun () -> fib_aux (n, 0, 1)
看起来没有什么特别的——一个非常简单的工作流。现在让来点有趣的——是时候仔细看看工作流引擎了。
当然,每个可靠的工作流引擎都应该管理线程,并尽可能在并行(或不并行?)线程中执行所有工作流和活动,并尽力提高性能。因此,引擎也遵循这一传统,使用CCR来执行所有活动(可以通过“let!
”关键字识别活动)。这里有两个活动,每个活动在幕后都被转换成CCR任务。每个活动(CCR任务)的输出被作为下一个活动(CCR任务)的输入。
在展示实际实现之前,想再描述一下工作流引擎的另一个特性。
每个可靠的工作流引擎都应该做的第二件事是为开发人员提供在活动执行期间注入方面的能力。让定义一个简单的安全方面,它旨在通知某人(可能是系统管理员)关于安全警报(假设在公司中,计算斐波那契数是被禁止的):
let securityAspect() =
printfn "Security Alert: Wake up, someone is running your workflow!!!"
这是一个非常简单的方面。可以让它变得更复杂(例如,在方面中验证活动输入)……也许下次吧。
现在是时候看看工作流引擎的代码了(顺便说一句,请原谅在这里使用“引擎”这个词,在F#中通常使用的是工作流构建器这个词):
type Activity<'a> = unit -> 'a
let runWorkflow (f:Activity<'a>) = f()
type WorkflowEngine() =
let dispatcher = new Dispatcher(8, "MyDispatcher") //from Microsoft.Ccr.Core
let queue = new DispatcherQueue("MyQueue", dispatcher) // from Microsoft.Ccr.Core
member b.Let(p, rest) =
rest p
member b.Return(x) =
fun () -> x
member b.Delay f =
fun () -> (runWorkflow f)()
member b.Bind(p, rest) =
securityAspect()
fun () ->
let res = queue.Enqueue(new Task(
fun () ->
printfn "Thread id is: %d"
Thread.CurrentThread.ManagedThreadId
let res = p()
let b = queue.Enqueue(new Task(
fun () ->
let fakeVal =
rest(res)()
()
))
))
new 'b()//return fake value - just to fool the F# compiler :)... sorry,
//it's ugly, but I haven't yet found a better way
工作流中的每个关键字都被转换成工作流构建器中的方法:let -> Let
,let! -> Bind
,return -> Return
,并且Delay
方法用于提供整个工作流的调用点。实际上,上面提到的所有工作流引擎特性相关的功能都是在Bind
方法中实现的。可以定义其他关键字,如for, while, use, if/then
等。在这里,只实现了所有可能关键字的基本集合。
重要的一点是,借助F#编译器的帮助,可以合理地控制工作流中的每一行代码,并可以使用方面或其他想要的方式对其进行影响。