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

In gen­eral, when you see async void in your code it’s bad news, because:

  1. you can’t wait for its com­ple­tion (as men­tioned in this post already)
  2. any unhan­dled excep­tions will ter­mi­nate your process (ouch!)

 

Sup­pose you have a timer event that fires every once in a while and you want to do some asyn­chro­nous pro­cess­ing inside the han­dler, so you pro­ceed to write some­thing along the lines of:

The fact that your timer event excepted might hurt, that it took your whole process with it is likely to hurt an awful lot more!

Well, what are your options? If you change the han­dler to return Task instead the code won’t com­pile because ElapsedE­ven­tHandler del­e­gate type spec­i­fies a void return type. Annoyed

Two options springs to mind here.

First, you can always just exe­cute the whole block of code syn­chro­nously instead.

This is not ideal because it’s going to block the thread whilst it’s wait­ing for the asyn­chro­nous oper­a­tion to come back, pre­vent­ing the thread from being used to do other use­ful work in the mean time.

If you wish to per­sist with using async-await and just wish to swal­low any excep­tions then you can con­sider option two:

In this case, we’ve sim­ply used a con­tin­u­a­tion (which fires regarded whether the pre­ced­ing task had faulted) to swal­low any excep­tion that had been thrown by the asyn­chro­nous operations.

 

Hope this helps, and remem­ber, when­ever you see async void in your code it should be trig­ger­ing off your spi­der senses!

Share