Making Asynchronous WCF calls without SvcUtil

On the course of last few months, I’ve been work­ing with Craig Neuwirt, on what I con­sider one of the coolest addi­tions to Cas­tle WCF Inte­gra­tion Facil­ity.

Prob­lem

As you prob­a­bly know by default all WCF calls are syn­chro­nous – you make a request, under the cover WCF blocks your thread using a Wait­Handle wait­ing for response, then it unblocks your threads mak­ing it look like a local call. This makes things sim­ple for a pro­gram­mer, who does not have to deal with syn­chro­niza­tion, but it’s an overkill from scal­a­bil­ity and per­for­mance perspective.

There are also one way calls, often mis­tak­enly called ‘fire and for­get’ which are still syn­chro­nous, but return con­trol to your thread as soon as they get con­fir­ma­tion from the other end that mes­sage was received.

There are also actual asyn­chro­nous calls, but to take advan­tage of this you have to either use svcu­til to gen­er­ate your code, or build asyn­chro­nous ver­sions of your con­tracts man­u­ally, which is tedious, forces you to remem­ber quite a lot details about how asyn­chro­nous con­tracts should look like, and there’s no way com­piler will tell you your sync and async con­tracts are out of sync (no pun intended).

Solu­tion

Using trunk ver­sion of WCF Facil­ity you can take advan­tage of a new mech­a­nism that lets you per­form asyn­chro­nous WCF calls hav­ing just the syn­chro­nous con­tract. Let me show you an example.

asyncwcf_solution

Con­tract project holds the ser­vice con­tract that both client and ser­vice share:

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    string MyOperation(string message);
}

Notice there’s no asyn­chro­nous ver­sion of the operation.

Ser­vice project con­tains only sim­ple imple­men­ta­tion of the con­tract plus con­sole host.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyService : IMyService
{
    public string MyOperation(string message)
    {
        Console.WriteLine("Called non one way operation... with " + message);
        Thread.Sleep(1000);
        Console.WriteLine("Done calculating");
        return message.ToUpperInvariant();
    }
}

It uses Thread.Sleep to sim­u­late some lengthy oper­a­tion, so that we can actu­ally see that client does not wait for the end of the operation.

The ser­vice host uses WCF Facil­ity to con­fig­ure the service:

class Program
{
    static void Main(string[] args)
    {
        using (StartService())
        {
            Console.WriteLine("Running");
            Console.ReadKey(true);
        }
    }
 
    private static IWindsorContainer StartService()
    {
        return new WindsorContainer()
            .AddFacility<WcfFacility>()
            .Register(Component.For<MyService>().ActAs(
                        new DefaultServiceModel().AddEndpoints(
                            WcfEndpoint.ForContract<IMyService>()
                                .BoundTo(new NetTcpBinding())
                                .At("net.tcp://localhost/Service"))));
    }
}

Client project is even simpler:

class Program
{
    static void Main()
    {
        var container = ConfigureContainer();
 
        var client = container.Resolve<IMyService>("operations");
        for(int i=0;i<10;i++)
        {
            Console.WriteLine("Asking: " + i);
            client.BeginWcfCall(
                s => s.MyOperation("Operation for " + i.ToString()),
                asyncCall => Console.WriteLine(asyncCall.End()),
                null);
        }
        Console.ReadKey(true);
    }
 
    private static WindsorContainer ConfigureContainer()
    {
        var container = new WindsorContainer();
        container.AddFacility<WcfFacility>().Register(
            Component.For<IMyService>()
                .Named("operations")
                .ActAs(new DefaultClientModel
                           {
                               Endpoint = WcfEndpoint.
                                   BoundTo(new NetTcpBinding()).
                                   At("net.tcp://localhost/Service")
                           }));
        return container;
    }
}

It con­fig­ures Wind­sor con­tainer and WCF Facil­ity, then it obtains client proxy for the ser­vice (again, syn­chro­nous con­tract) and uses some WCF Facil­ity magic to per­form calls asyn­chro­nously. Let’s run it and see:

asyncwcf_program

As you can see, on the client we first issued all 10 requests, then we grad­u­ally received responses from the server.

So how do I use it?

Dis­cus­sion

As you can see in the code above, instead of call­ing MyOp­er­a­tion directly on proxy I used Begin­Wcf­Call exten­sion method from WCF Facil­ity and passed there a del­e­gate with invo­ca­tion of the method, plus two more argu­ments. The sec­ond and third argu­ments can be either Async­Call­back, and object, like in stan­dard .NET async pat­tern, or (as in my exam­ple) IWcfAsyncCall<T> (or it’s non-generic ver­sion for meth­ods that don’t return any value).

asyncwcf_IWcfAsyncCall

IWc­fA­sync­Call itself inher­its from IAsyn­cRe­sult and adds con­ve­nience meth­ods to end async invo­ca­tion. The over­load with out argu­ments are there to han­dle meth­ods that have ref or out argu­ments. Yes – this means you can use that also for meth­ods with out and ref argu­ments. One more note­wor­thy fea­ture is usage of Syn­chro­ni­sa­tion­Con­text, which means it’s safe to update WinForms/WPF GUI from the end oper­a­tion thread.

The code is avail­able now, take it, use it, tell us what you think.

Tech­no­rati Tags: ,
  • Gau­thier Segay

    Hi Krzysztof, I didn't get my hand dirty with that right now but the over­all solu­tion looks really clever!

    Kudos to Craig and you for such an overhaul!

  • Fred Legrain

    Hi,

    Thanks for that great work, it looks pretty and match per­fectly a part of my cur­rent concerns.

    I am doing dynamic asyn­chro­nous WCF calls. Thanks to you, this is now easy… From clas­sic .Net.

    Now, I con­sider port­ing your DPWCF.Proxy to Sil­verlight.
    What you think? Is that pos­si­ble? What could be the major issues on the path?

  • http://kozmic.pl/Default.aspx Krzysztof Koźmic

    @Fred Legrain
    We looked at mov­ing this to Sil­verlight, and decided it would be quite hard actu­ally. First of all, Sil­verlight WCF works quite dif­fer­ently as com­pared to .NET ver­sion. This means the approach I used in my spike (DPWCF) won't work at all — Sil­verlight just has no API it uses.

    You might try to build sim­i­lar thing on top of Dynamic Proxy 2.2, but that would basi­cally mean start­ing from scratch and since Sil­verlight 3.0 does not allow bina­ries shar­ing between SL and .NET you would have to share your con­tract at a code level, and com­pile them twice, which is not a big issue most of the time — rather inconvenience.

    If you'd like to help with this effort, go to cas­tle users group and start a thread about it.

  • Fred Legrain

    That's what I fig­ured out quickly after putting your code in a Sil­verlight project.
    I plan going explore some other path that I have in mind.

    Thank you,
    Fred­eric

  • http://kozmic.pl/Default.aspx Krzysztof Koźmic

    @Fred

    let me know if you come up with some­thing promis­ing. We could team up on this one.

  • Christo­pher Smith

    The link to your project appears to be bro­ken. Can you sup­ply a valid path to the project? Thanks!

  • http://kozmic.pl/Default.aspx Krzysztof Koźmic

    fixed, thanks for noticing