在这篇文章中,我们将关注一个常见的异步模式:反馈进度的事件(Reporting Progress with Events)。在文章最后,我们会使用这个设计模式开发一个示例,从Twitter中获取一系列记录。
这是F#异步编程基础系列的第二部分。其中部分示例代码来自于F# JAOO Tutorial。
第1部分描述了F#作为一个并行及异步语言,是如何支持轻量级的响应操作,并给出了CPU异步并行和I/O异步并行两种模式。
第2部分便是本文。
第3部分则描述了F#中轻量级,响应式的独立代理对象。
模式3:反馈进度的事件
我们先来看一下这个设计模式的一个基础示例。在下面的代码中,我们会定义一个对象,以此来协调一组同时执行的异步任务。每个任务在结束之后会主动汇报它的结果,而不是等待统一的收集过程。
type AsyncWorker<'T>(jobs: seq<Async<'T>>) =
// This declares an F# event that we can raise
let jobCompleted = new Event<int * 'T>()
/// Start an instance of the work
member x.Start() =
// Capture the synchronization context to allow us to raise events back on the GUI thread
let syncContext = SynchronizationContext.CaptureCurrent()
// Mark up the jobs with numbers
let jobs = jobs |> Seq.mapi (fun i job -> (job,i+1))
let work =
Async.Parallel
[ for (job,jobNumber) in jobs ->
async { let! result = job
syncContext.RaiseEvent jobCompleted (jobNumber,result)
return result } ]
Async.Start(work |> Async.Ignore)
/// Raised when a particular job completes
member x.JobCompleted = jobCompleted.Publish
设计模式的一些关键之处已经使用黄色进行高亮:
在对象的Start方法中,我们在GUI线程中捕获了当前的“同步上下文”,这使得我们可以从GUI的上下文中运行代码或触发事件。我们还定义了一个私有的辅助函数来触发任意的F#事件,这虽不必须但可以使我们的代码变的更为整洁。
定义了多个事件。这些事件作为属性发布,如果该对象还需要被其他.NET语言使用,则为它标记一个[<CLIEvent>]属性。
我们这里通过指定一个定义了任务内容的异步工作流来启动后台任务。Async.Start可以用来启动这个工作流(虽然Async.StartWithContinuations更为常用,例如在后面的示例中)。在后台任务产生进度之后,便会在合适的时候触发这些事件。
这段代码使用了两个基于System.Threading.SynchronizationContext的辅助方法,它们会在这个系列的文章中多次出现。如下:
type SynchronizationContext with
/// A standard helper extension method to raise an event on the GUI thread
member syncContext.RaiseEvent (event: Event<_>) args =
syncContext.Post((fun _ -> event.Trigger args),state=null)
/// A standard helper extension method to capture the current synchronization context.
/// If none is present, use a context that executes work in the thread pool.
static member CaptureCurrent () =
match SynchronizationContext.Current with
| null -> new SynchronizationContext()
| ctxt -> ctxt