35

In C# is there any difference between using a delegate to do some work asynchronously (calling BeginInvoke()) and using a ThreadPool thread as shown below

public void asynchronousWork(object num)
    {
        //asynchronous work to be done
        Console.WriteLine(num);
    }

 public void test()
    {
        Action<object> myCustomDelegate = this.asynchronousWork;
        int x = 7;

        //Using Delegate
        myCustomDelegate.BeginInvoke(7, null, null);

        //Using Threadpool
        ThreadPool.QueueUserWorkItem(new WaitCallback(asynchronousWork), 7);
        Thread.Sleep(2000);
    }

Edit:
BeginInvoke makes sure that a thread from the thread pool is used to execute the asynchronous code , so is there any difference?

Jason Down
  • 21,731
  • 12
  • 83
  • 117
nighthawk457
  • 1,102
  • 3
  • 12
  • 27
  • 1
    possible duplicate of [Does Func.BeginInvoke use the ThreadPool?](http://stackoverflow.com/questions/3556634/does-funct-begininvoke-use-the-threadpool) – nothrow Apr 26 '12 at 20:26
  • I am not sure if your comment was before I edited my question but I am aware that BeginInvoke executes delegates on threads belonging to the threadpool . My question was more on whether there was any difference between explicitly executing the method on a thread pool using QueueUserWorkItem or by using BeginInvoke – nighthawk457 Apr 26 '12 at 20:41

1 Answers1

36

Joe Duffy, in his Concurrent Programming on Windows book (page 418), says this about Delegate.BeginInvoke:

All delegate types, by convention offer a BeginInvoke and EndInvoke method alongside the ordinary synchronous Invoke method. While this is a nice programming model feature, you should stay away from them wherever possible. The implementation uses remoting infrastructure which imposes a sizable overhead to asynchronous invocation. Queue work to the thread pool directly is often a better approach, though that means you have to co-ordinate the rendezvous logic yourself.

EDIT: I created the following simple test of the relative overheads:

int counter = 0;
int iterations = 1000000;
Action d = () => { Interlocked.Increment(ref counter); };

var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
for (int i = 0; i < iterations; i++)
{
    var asyncResult = d.BeginInvoke(null, null);
}

do { } while(counter < iterations);
stopwatch.Stop();

Console.WriteLine("Took {0}ms", stopwatch.ElapsedMilliseconds);
Console.ReadLine();

On my machine the above test runs in around 20 seconds. Replacing the BeginInvoke call with

System.Threading.ThreadPool.QueueUserWorkItem(state =>
{
    Interlocked.Increment(ref counter);
});

changes the running time to 864ms.

Lee
  • 142,018
  • 20
  • 234
  • 287
  • 1
    I feel another knee-jerk reaction coming on? We need some empirical data to objectify *a sizable overhead to asynchronous invocation.* What is the trade-off involved by having *to co-ordinate the rendezvous logic yourself...* versus the syntactic sugar of the generally accepted asynchronous implementations - e.g. BeginInvoke, Callback, EndInvoke. One thing we get out of using IAsyncResult is a handle from which we can monitor what is going on. I have never actually tried anything using the ThreadPool to see if I could do the same. – IAbstract Apr 26 '12 at 21:58
  • 1
    I wonder why each delegate defines a `BeginInvoke` method which asynchronously starts the delegate? The only aspect of `BeginInvoke` which would seem bound to any particular delegate signature would be the process of converting a delegate plus parameters into a unified item, which could have been done just as well if each delegate defined `Bind` method which took the same parameters as the delegate and returned a `MethodInvoker`. – supercat Apr 26 '12 at 22:18
  • 1
    @IAbstract - This is specifically about `Delegate.Begin\EndInvoke` not the general `BeginInvoke\EndInvoke` pattern found in the framework. It's true that `IAsyncResult` gives you more control, however it is being abandoned in favour of `Task` in .net 4 and above. – Lee Apr 26 '12 at 22:41
  • 1
    @Lee: I was wondering about the use of `Task` in place of the older pattern and `IAsyncResult`. Thanks for the clarification and some empirical data! – IAbstract Apr 27 '12 at 13:40
  • 1
    @Lee: I came up with some very different results for the first test - `Action.BeginInvoke(...)`: ~10 seconds every run which averages about 100 executions/ms. `ThreadPool` enqueueing averages 2.2k executions/ms while `Task.Start()` averages 1.7k executions/ms. I can post my tests and relevant codes if you wish to double-check my results. ;) To keep my testing in line with yours, I use the same `Interlocked.Increment(ref counter)` in my delegate method. – IAbstract Apr 28 '12 at 16:02