Just before Xmas I attended Liam Westley’s talk at the NxtGen user group meeting on Async, where he covered the Task-asynchronous pattern with a great series of examples. He demonstrated the use of various combinators such as WhenAll and WhenAny which are defined on the Task type.
During the talk I started wondering about how you might implement WhenAny. I knew that the TPL offers a ContinueWith method on a Task, and using this and subscribing to all of the tasks supplied to the method, and using some synchronisation primitive to monitor the completion events it looked like you’d be able to implement this yourself. However, I couldn’t remember if Task offered a way to unsubscribe from ContinueWith, which is something you’d need in order efficiently cancel if any of the tasks throws an error.
Task is a really nice type that allows you to construct a tree of dependencies, then start one of the tasks which will start the execution of later elements in the tree. For example, in the following code we set up tasks x and y, configuring z to start when one of x or y is finished, and having t depend on z finishing.
If we set a breakpoint on the line with the Start method, if we inspect x in the watch window we can see that it contains a field m_completionObject,
You could write code of the Invoke method on this type yourself.
TrySetResult is the gateway that prevents the completion being set more than once, but I couldn’t recall a public way of unsubscribing after doing a ContinueWith, and the code above calls RemoveContinuation so it looked like the continuation task might be released after the task had finished. I was confused when I looked in the debugger and noticed that the completion hadn’t been unset. I got out Reflector Pro and set a breakpoint on the RemoveContinuation method, only to notice that executes code only if the m_continuationObject contains a List<> rather than a single subscriber.
In summary, one could (more or less) implement WhenAny using publically exposed items.