All about Async/Await, System.Threading.Tasks, System.Collections.Concurrent, System.Linq, and more…
(The full set of ParallelExtensionsExtras Tour posts is available here.)
Delegates in .NET may have one or more methods in their invocation list. When you invoke a delegate, such as through the Delegate.DynamicInvoke method, the net result is that all of the methods in the invocation list get invoked, one after the other. Of course, in a parallel world we might want to invoke all of the methods in the invocation list in parallel instead of sequentially. The DelegateExtensions.cs file in ParallelExtensionsExtras provides an extension method on Delegate that does just that.
public static object ParallelDynamicInvoke(
this Delegate multicastDelegate, params object args)
.Select(d => d.DynamicInvoke(args))
This ParallelDynamicInvoke extension method accepts the target multicast delegate as well as the params array to provide to the delegate invocation, just as with Delegate.DynamicInvoke:
public object DynamicInvoke(params object args);
ParallelDynamicInvoke uses the delegate’s GetInvocationList method to retrieve an array of all of the methods in the invocation list, each of which is represented as a Delegate. Then, PLINQ is used to invoke each individual delegate, returning the last delegate’s result.
Would the normal serial invocation also return only the result of the last invocation?
It seems odd to throw away all of the other results.
Yes, and those were the semantics I was trying to mimic here for the parallel implementation. Of course, you wouldn't have to do that. You could, for example, replace the ".Last()" with a ".ToArray()" and you'll have all of the results returned, not just the last.
I like the idea, and I would *love* to use it with events.
However, one standard event pattern includes passing the args from event handler to event handler, allowing each to potentially modify the args (for example, setting Cancel or Handled to true). Doing this in parallel introduces problems, since the args are potentially being used concurrently. Introducing locking would remove the benefit of the args unless you were smart about it.
Just something to beware of, I suppose. :)
Thanks for the comment, Keith. That's definitely true: if there's a strict dependency between the delegates, they can't be invoked in parallel. In most cases with this kind of situation, though, are there typically such real dependencies? I've seen event handlers that set Cancel or Handled to true, but I've not seen one (at least not that I can remember) that reverts the decision of a previous handler to set it to default of false. This makes sense, since event handlers typically don't know the order in which they were registered and thus don't know in what order they'd be invoked. If the directionality of the bit flag being set is just from false to true, then theoretically you could still do these in parallel (especially if the decision of other delegates isn't exposed, such as through a SetHandled method rather than a get/set property).
toub is correct in that event handlers are not guaranteed to execute in order, as there is no "order"... the only way to chain events together would be if A.event calls B.EventHandler which fires B.event which calls C.EventHandler... but this could be handled in parallel as any other eventhandler attached to A.event.
Quite frankly, I would expect event handlers to be one of the first places to easily take advantage of the Parallels namespace.
On that note, I do think it'd make sense for the e.Cancel to somehow cause a Stop or Break in the parallel execution... not sure quite how such semantics would be worked out, but I'm sure given an hour of thinking, a solution would be obvious.
And, yes, you should be able to end the parallel execution if the particular property you were interested in on the event arg got set.
public event HandledEventHandler MyEvent;
private void RaiseMyEvent()
var eventValue = MyEvent;
if (eventValue != null)
var handlers = (HandledEventHandler)eventValue.GetInvocationList();
var hea = new HandledEventArgs(defaultHandledValue:false);
Parallel.ForEach(handlers, (handler,loop) =>
if (hea.Handled) loop.Stop();
Stephen, I also noticed that you have the AsOrdered. If you didn't depend on order, I am assuming this can be removed, correct?
@Rich Crane: This isn't about the order of the execution of the delegates, but rather about ensuring that their outputs are ordered... that's just so that Last does in fact provide the last one based on the original ordering.