NOTE: if you’re unfa­mil­iar with how Post­Sharp works under the hood, I highly rec­om­mend that you check out Dustin Davis’ excel­lent Post­Sharp Prin­ci­ples series of blog posts here.

The Prob­lem

The new async/await key­words in C# are pretty awe­some, and makes life an awful lot eas­ier when writ­ing asyn­chro­nous and non-blocking IO code. How­ever, for those of us who are using frame­works such as Post­Sharp to deal with cross-cutting con­cerns we now face a new chal­lenge – the aspects which we have come to rely upon no longer works the way we expect them to when applied on async meth­ods (which returns void, Task or Task<T>), as can be seen from the exam­ples below:

So what’s wrong here?

If you take a look at the code from the above exam­ple using a decom­piler such as Jet­Brain’s Dot­Peek, you’ll see that the nor­mal syn­chro­nous ver­sion of Foo looks some­thing along the line of:

image

As you can see, the weaved code include calls to the OnEn­try, OnSuc­cess and OnEx­cep­tion meth­ods pro­vided by the OnMethod­Bound­aryAspect class, so every­thing is as expected here.

For FooA­sync how­ever, the pic­ture is a lit­tle more complicated:

image

Turns out the C# com­piler rewrites async meth­ods into a state machine which means that although the OnSuc­cess and OnEx­cep­tion hooks are still in place, they’re not telling us when the body of the method suc­ceeds or fails but instead, when the state machine cre­ation has suc­ceeded or failed!

image

image

Pretty big bum­mer, eh?

Pro­posed Solution

One way (and the best way I can think of for now) to get around this is to have a spe­cial aspect which works with meth­ods that return Task or Task<T> and hook up con­tin­u­a­tions to be exe­cuted after the returned tasks had fin­ished. Some­thing sim­i­lar to the below will do for the on method bound­ary aspect:

And then you can cre­ate a TraceA­sync attribute that works for async methods:

As you can see from the out­put above, our new OnTask­Fin­ished, OnTask­Faulted and OnTaskCom­ple­tion hooks are cor­rectly exe­cuted after the task returned by the async method had fin­ished, faulted due to excep­tion or ran to completion!

The same approach can also be applied to other built-in aspects such as the Method­In­ter­cep­tionAspect class.

Before you go…

How­ever, there are a two things you should con­sider first before jump­ing into the workaround pro­posed above.

1. if you look at the out­put from the pre­vi­ous exam­ple care­fully, you’ll see that the line “FooA­sync fin­ished” came AFTEREnter­ing Boo” even though from the test code we have awaited the com­ple­tion of FooA­sync before call­ing Boo. This is because the con­tin­u­a­tions are exe­cuted asynchronously.

If this behav­iour is not desir­able to you, there is a very sim­ple fix. Back in the OnA­syncMethod­Bound­Aspect class we defined above, sim­ply add TaskContinuationOptions.ExecuteSynchronously to each of the continuations:

image

2. the pro­posed solu­tion still wouldn’t work with async meth­ods that return void sim­ply because there are no returned Task/Task<T> objects to hook up con­tin­u­a­tions with. In gen­eral though, you should avoid hav­ing async void meth­ods as much as pos­si­ble because they intro­duce some pit­falls which you really wouldn’t want to find your­self in! I’ve dis­cussed the prob­lem with aysnc void (and some poten­tial workarounds) in a pre­vi­ous post here.

 

I hope this post proves use­ful to you, and happy PostSharp’ng! I hear some big things are com­ing in this space Winking smile

Share
  • http://stackoverflow.com/users/218882/floyd-pink Hari Menon

    Nice arti­cle. Let me try and bor­row the con­cepts here and try to apply them to the AOP done with Cas­tle Dynam­icProxy and Struc­tureMap, as detailed here — http://weblogs.asp.net/thangchung/archive/2011/01/25/aop-with-structuremap-container.aspx

  • http://www.jtenosllc.com/ Joe Enos

    Line 15 of OnA­syncMethod­Bound­aryAspect: Is that sup­posed to be Return­Type instead of ReflectedType?

  • http://www.jtenosllc.com/ Joe Enos

    Actu­ally, I think it needs to be:
    (methodInfo.ReturnType.IsGenericType && methodInfo.ReturnType.GetGenericTypeDefinition() != typeof(Task))

    The return type of the method might be typeof(Task), which is never equal to typeof(Task), so you’d need to do this generic conversion.

  • http://www.jtenosllc.com/ Joe Enos

    Stu­pid Word­Press com­ment cleaner thing…

    There are angle brack­ets after the Tasks in that pre­vi­ous com­ment (I’ll put square brack­ets here):

    (methodInfo.ReturnType.IsGenericType && methodInfo.ReturnType.GetGenericTypeDefinition() != typeof(Task[]))

    The return type of the method might be typeof(Task[Foo]), which is never equal to typeof(Task[]), so you’d need to do this generic conversion.

  • the­burn­ing­monk

    @Joe Enos — you’re right, intel­lisense got the bet­ter of me there! In the ver­sion I ended up using I actu­ally sim­pli­fied it and store a bool flag so to avoid doing these checks at runtime:

    pro­tected bool IsAs­ync { get; set; }

    pub­lic over­ride void CompileTimeInitialize(MethodBase method, Aspect­Info aspect­Info)
    {
    var method­Info = method as Method­Info;
    if (method­Info == null)
    {
    throw new Exception(“MethodInfo is null”);
    }

    IsAs­ync = typeof(Task).IsAssignableFrom(methodInfo.ReturnType);

    base.CompileTimeInitialize(method, aspect­Info);
    }

    so that my attribute is com­pat­i­ble with both async and sync meth­ods, and at run­time, I only hook up the task con­tin­u­a­tions for async meth­ods, can’t share the rest of that class, but you should get the idea easy enough I suppose.