Following on from my BeginInvoke()/EndInvoke() question, are there major differences in performance/anything else between Delegate.BeginInvoke() and using QueueUserWorkItem() to invoke a delegate asynchronously?
-
You might also want to see: http://marcgravell.blogspot.com/2009/02/async-without-pain.html – Marc Gravell Feb 11 '09 at 08:50
6 Answers
The main thing I can think of with QueueUserWorkItem
is that you have to use the WaitCallback
delegate type, which looks tricky if you already have a SomeRandomDelegate
instance and some args. The good news is that you can fix this with a closure:
ThreadPool.QueueUserWorkItem(
delegate { someDelegate(arg1, arg2); }
);
This pattern also ensures you get proper strong typing at compile time (unlike passing an object
state arg to QueueUserWorkItem
and casting it in the target method). This pattern can also be used when calling methods directly:
ThreadPool.QueueUserWorkItem(
delegate { SomeMethod(arg1, arg2); }
);
Obviously, without an EndInvoke
equivalent, you also can't get a return value back out unless you call a method / raise an event / etc at the end of your method... on a related note, you need to be careful with exception handling.

- 1
- 1

- 1,026,079
- 266
- 2,566
- 2,900
-
3Encountering this exact scenario was the first time I thought to myself, "Wow, I love closures." – Greg D Feb 10 '09 at 15:41
-
Wow, this is a supremely tip. It feels like a boxing / unboxing semantic, and I imagine in no more costly, in broad scheme of things. – EnocNRoll - AnandaGopal Pardue Mar 12 '09 at 15:14
-
Oops, technically, it should be "delegate(object o)" or similar in order for the code to compile, I believe, since QueueUserWorkItem expects a delegate signature that matches WaitCallback, i.e., one that includes a single parameter of type object. – EnocNRoll - AnandaGopal Pardue Mar 19 '09 at 14:47
-
4@EnocNRoll - no; *lambdas* must declare the right parameters; anon-methods, however, can choose to ignore parameters. The code is correct. – Marc Gravell Mar 19 '09 at 21:41
-
You are absolutely correct! That's a very interesting nuance. I finally got a chance to re-test this. I think the use of a lambda to wrap the call to QueueuserWorkItem was inadvertently giving me the wrong impression. Thanks for the response and correction! – EnocNRoll - AnandaGopal Pardue Apr 14 '09 at 00:37
-
@Marc If I'm updating a list that is passed into `SomeMethod(object state)`, I have to cast in order for the original reference to update: `((List
)state).Add("hello");`. Does the closure technique avoid this, or are you dealing with a copy in SomeMethod? – Chris S Mar 16 '10 at 12:33 -
Strong typing is my favourite reason to use QUWI. After all, if my compiler is going to catch typing issues, why not make the most of it? – Wayne Werner Oct 16 '12 at 12:39
-
+1 for being careful about exceptions... I've had my application die on another thread silently several times before. – Jeff B Oct 16 '12 at 12:41
http://blogs.msdn.com/cbrumme/archive/2003/07/14/51495.aspx
says:
"One surprising fact is that this is also why Delegate.BeginInvoke / EndInvoke are so slow compared to equivalent techniques like ThreadPool.QueueUserWorkItem (or UnsafeQueueUserWorkItem if you understand the security implications and want to be really efficient). The codepath for BeginInvoke / EndInvoke quickly turns into the common Message processing code of the general remoting pathway."

- 21,465
- 8
- 65
- 74

- 443
- 4
- 11
-
Hmmm, thanks, that's both interesting and very scary. I think I will roll back any BeginInvoke()/EndInvoke() code that doesn't require return values! – endian Feb 10 '09 at 16:33
The EndInvoke() has a useful but rarely mentioned behavior - it rethrows all unhandled exceptions that the delegate generated in the context of the original thread so you can move the exception processing logic into the main code.
Also, if your delegate has out/ref parameters, they will be added to the EndInvoke() signature allowing you to get them when method finishes execution.

- 378
- 3
- 8
If you call ThreadPool.QueueUserWorkItem, exceptions raised in the work item will be unhandled on the background thread (unless you explicitly catch them). In .Net 2 and above this will terminate your AppDomain.
If you call delegate.BeginInvoke() then exceptions are queued to be re-thrown when EndInvoke() is called. If you never call EndInvoke(), then the exceptions are essentially 'leaked' memory (as is any other state not released by the async operation).

- 4,174
- 34
- 47
There should not be any big difference, I also think that the generated BeginInvoke/EndInvoke for a delegate uses the thread pool to execute.

- 1,093
- 1
- 7
- 15
There shouldn't be any performance difference, as both Delegate.BeginInvoke and ThreadPool.QueueUserWorkItem will execute on a thread pool thread.
The biggest difference is that if you call BeginInvoke, you're obliged to call EndInvoke at some point. In contrast, ThreadPool.QueueUserWorkItem is "fire and forget". That has benefits and drawbacks. The benefit being that you can forget about it. The drawback being that you have no way of knowing, unless you add your own synchronization/notification mechanism, when the task has completed.

- 131,090
- 20
- 188
- 351