AOP – using PostSharp attributes with async Task/Task<T> methods

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

The Problem

The new async/await key­words in C# are pret­ty awe­some, and makes life an awful lot eas­i­er when writ­ing asyn­chro­nous and non-block­ing IO code. How­ev­er, for those of us who are using frame­works such as Post­Sharp to deal with cross-cut­ting 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­pil­er 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­vid­ed by the OnMethod­Bound­aryAspect class, so every­thing is as expect­ed here.

For FooA­sync how­ev­er, the pic­ture is a lit­tle more com­pli­cat­ed:

image

Turns out the C# com­pil­er 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­ceed­ed or failed!

image

image

Pret­ty big bum­mer, eh?

Proposed 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­cut­ed 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 meth­ods:

As you can see from the out­put above, our new OnTask­Fin­ished, OnTask­Fault­ed and OnTaskCom­ple­tion hooks are cor­rect­ly exe­cut­ed after the task returned by the async method had fin­ished, fault­ed due to excep­tion or ran to com­ple­tion!

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

Before you go…

How­ev­er, there are a two things you should con­sid­er 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­ful­ly, you’ll see that the line “FooA­sync fin­ished” came AFTER “Enter­ing Boo” even though from the test code we have await­ed the com­ple­tion of FooA­sync before call­ing Boo. This is because the con­tin­u­a­tions are exe­cut­ed asyn­chro­nous­ly.

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 con­tin­u­a­tions:

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­er­al 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 real­ly 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 hap­py PostSharp’ng! I hear some big things are com­ing in this space Winking smile