| Frank 的个人资料Frank de Groot日志列表 | 帮助 |
|
1月29日 Using F#'s async workflows to invoke a WCF serviceIn addition to Ted Neward's posts on creating a WCF service in F# I'd like to share a howto on calling a WCF service asynchronously from F# using the infamous async workflows.
I'll start off with a simple data contract. This is not required, you could use only primitive type parameters, but I like data contracts. The easiest way is to create a record type. Unfortunately the serializer requires mutable fields, but I can live with that. Please note I'm skipping the open System.Runtime.Serialization and open System.ServiceModel.
[<DataContract>] [<DataMember>] mutable MyParameter:string; } [<DataContract>] [<DataMember>] mutable MyResultParameter:string; } Then I'll define an interface for my service in F#:
[<ServiceContract>] Now to invoke a service asynchronously I need to create a new interface with a slightly different signature. For WCF this new interface is equivalent to the one above, except that it allows asynchronous invocation. Because they need to have the same name I put them in a different assembly so I have one assembly for the contract at the server side and another assembly for the client side. We can reuse the data contract though.
[<ServiceContract>] Notice the Begin... and End... methods. By adding the AsyncPattern = true to the OperationContract WCF assumes that it's the asynchronous version of MyOperation. As you can see the signature is slightly different: BeginMyOperation expects an additional callback delegate and an optional state object and returns a 'handle' that is passed to EndMyOperation to get the result of invoking MyOperation. Note that here too I need to supply a name for the first parameter, in this case 'Request'. As Ted described WCF requires a name for the parameters specified in the interface definition. This is common in C# and VB so that's probably the reason why it wasn't noticed by the WCF team. Luckily it's easy to add in F#. The above code is enough to invoke a service asynchronously. But if we want to use it in async workflows we require another method named AsyncMyOperation. I decided to create an extension method that adds this method to the interface we just created:
module Essentially I'm creating a new method named AsyncMyOperation by calling Async.BuildPrimitive. Async.BuildPrimitive requires the Begin... and End... methods that are common in the .NET Framework for asynchronous operations. It then creates an Async<'a> that we can use in an async workflow. UPDATE: I previously created a local function to capture the request and handed that to Async.BuildPrimitive but I just noticed the overloads of BuildPrimitive that take additional arguments. You can supply up to three arguments. When you need to pass more (unlikely I'd say) you can always create a local function to capture additional arguments. Mind you, the module name I'm using in the code above really doesn't matter as long as you add an open Async where you want to use it. Right, now all we need to do is to create the async workflow to actually invoke the service:
let CallMyService = Please notice the exclamation mark in let! result = ... If you add a Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()) before and after the call you'll see that the code below the service call runs on a different thread than the lines above it.
Then, finally, you can run your async workflow with Async.Spawn CallMyService. You can use Async.Run but it would still block to wait for the result so that's probably not what you want.
Well, please let me know if this code works for you or not or any comments and questions you might have. |
|
|