While working with a client to create a new version of their software, one of the tasks was creating a service that talked to a hardware laser tracker, a device which allows tracking certain points (targets) over a large distance.
The API provided by the manufacturer was not ideal, to say the least. The API consisted of a managed wrapper over two classes, we’ll call them Request and Response, providing asynchronous request and callback operations. For each operation on the Request object, a corresponding Answer operation would arrive asynchronously on the Response object, e.g. if a
GetPosition method was called on Request, sometime later an
OnGetPositionAnswer method would arrive on the Response object, containing the relevant information.
One of the requirements was to be able to wait for several of such callbacks to arrive, before doing another operation. Several options were considered, among them Reactive Extensions (Rx) and TPL Dataflow. In the end, I chose an approach based on the
TaskCompletionSource<T> class (part of the TPL), which I describe below.
Having exposed a .NET event in the Response object for each of the callbacks we were interested in, I defined a following method, returning a
T was the object that contained the data I required:
And it worked great! The caller of
GetPositionAsync now had a task which he can either
await (if using .NET 4.5, or in .NET 4.0 using Async Targeting Pack (now known as Microsoft.Bcl.Async), but only if running in Visual Studio 2012), or using plain old methods, available on the Task object.
However, upon calling this method a second time, an
InvalidOperationException An attempt was made to transition a task to a final state when it had already completed. was thrown on the
tcs.SetResult(data) line. It took me a second to realize the bug, can you see it?
The problem occurred because the event handler was not unsubscribed from, after the task had completed! Since the handler is a lambda expression, it captured the variable
tcs when it was created. When we called
GetPositionAsync() the second time, we made another subscription to the event handler, but when it fired, the first subscription was handled first, attempting to set the result of the first
TaskCompletionSource instance, which had, of course, already finished.
Unsubscribing from an anonymous method (or a lambda expression) is possible, but not very pretty. It requires a small abuse of the C# syntax, to have the handler stored in a local variable, then subscribed and unsubscribed from. After a bit of tweaking, this was the end result, solving the problem:
It’s not very pretty, but it does the job. If anyone has a better suggestion, please leave a comment!
Until next time, happy asyncing!