Debouncing asynchronous operations

With the introduction of the async and await keywords, asynchronous programming has finally become achievable by any .Net developer.  Building on the TPL, async and await extend the C# language specification to make task based programming relatively straight forward, and is a huge step forward from APM.

However, being able to write code that runs asynchronously is only the first step, thinking asynchronously is a whole new set of skills that developers have to master.  For example, how should you access a shared resource from asynchronous code?

There are some great resources out there and one of my favourite blogs is produced by Microsoft’s Stephen Toub who works with the PFXTeam.  A while ago I came across a real gem in this blog, the AsyncSemaphore, which allows us to create an AsyncLock; which, in turn, helps you answer the question above.

But what else can we use an AsyncLock for?  Lots of stuff!

One particularly useful use is to debounce an asynchronous function.  Imagine we have a long running asynchronous function that goes and gets the latest stock price from a web service, if we allow multiple threads to execute the method then it would result in many calls to the same web service, all of which would return the same answer.  A better approach is to run the method once and allow anyone else that asks for the result to wait for the first execution to complete, and return the same result.

Using AsyncLock we can create a class for that:

This allows you to create an asynchronous function that will only have one instance running at any time, any multiple calls to the same function will all wait for the result of the currently executing function.  You can also use the minimumGap and duration parameters to cache the result of a successful execution for a period, this is great when you know that an expensive operation’s result doesn’t change too frequently, or that it’s not necessary to have real time, ‘fresh’ results (e.g. when serving a web page).

For example, the code below:

Produces the following output:

The code tries to run the function three times at once.  The first execution is set to timeout after 0.5s, whereas the second and third executions don’t have a timeout.  The function itself is set to take 1s, therefore the first task will be cancelled before it get’s the opportunity to complete (after 0.5s), which is OK, because the 2nd task will now start immediately, as the first task failed and there is no result yet.  The second task runs to completion, which means the third task (which was also waiting can make use of the result without running the function again.

Debouncing operations can have a huge impact on the performance of real world systems, so I hope you find this piece of code as useful as I do!

 

Comments 1

Leave a Reply

%d bloggers like this: