Difference between delegate.BeginInvoke and using ThreadPool threads in C#
Asked Answered
P

1

36

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?

Pinter answered 26/4, 2012 at 20:21 Comment(2)
possible duplicate of Does Func<T>.BeginInvoke use the ThreadPool?Smiley
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 BeginInvokePinter
F
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.

Fractocumulus answered 26/4, 2012 at 20:47 Comment(5)
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.Elephantine
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.Perverted
@Elephantine - 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.Fractocumulus
@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!Elephantine
@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.Elephantine

© 2022 - 2024 — McMap. All rights reserved.