Click here to Skip to main content
11,647,659 members (68,562 online)
Click here to Skip to main content

Fast Asynchronous Delegates in .NET

, 1 Jul 2009 CPOL 34.2K 266 55
Rate this:
Please Sign up or sign in to vote.
Implementation of delegates which are way faster during async operations than the default delegates in .NET

Introduction

This article describes how to implement a delegate which performs much better during BeginInvoke/EndInvokeoperations, than the delegates with default implementation of BeginInvoke/EndInvoke injected by CLR. The idea is rather simple, no cheats with IL and function pointers.

Background

In .NET, there is 3 ways to execute an operation or a task asynchronously. The first approach consists in using of System.Threading.ThreadPool, which suits the best in most cases. The second approach is Thread.Start, which fits better than ThreadPool, for the cases of long-running operations. And the last one is APM pattern (pair of methods BeginSomething/EndSomething) provided by all delegates and some major classes, like Stream, WebRequest, etc. APM pattern suits the best when you need to retrieve a result of an asynchronous operation.

Let's consider more closely delegates in .NET. Each delegate in .NET is decorated by C# compiler with such methods as BeginInvokeand EndInvoke. These methods at some degree are abstract- C# compiler does not provide any implementation at compile time. Implementation is injected later by CLR. BeginInvoke/EndInvokeseems to be the perfect choice when we need to retrieve a result or an absence of a result (which is called an exception) of an asynchronous operation. But the "perfect" word here is valid only in the scope of logical design. When it comes to performance, be careful: "Avoid using BeginInvoke/EndInvokemethods as much as possible. The reason is both methods internally use remoting infrastructure." - this statement I've recently encountered in one of the Microsoft's books (I've rephrased it a little bit). Basically, I was interested in the fact, how much slower than ThreadPool, for instance, are those two methods. Therefore I've implemented my own versions of BeginInvoke/EndInvoke.

Using the Code

The usage of my APM pattern implementation is exactly the same, as the default one. Here is an example:

var fastDel = new FastInvokeDelegate<int>(() => MyAsyncTask(100));
var asyncResult = fastDel.BeginInvokeFast(null, null);
//do something else in parallel
int result = fastDel.EndInvokeFast(asyncResult);

Here MyAsyncOperationis some "don't-matter" function which returns an integer. As I mentioned, the usage of BeginInvokeFast/EndInvokeFastis exactly the same as BeginInvoke/EndInvoke. FastInvokeDelegatehere is just a simple regular delegate decorated with extension methods BeginInvokeFast/EndInvokeFast. Looks a bit cumbersome. Unfortunately, C# compiler at the moment does not provide any better language constructs to make it more nice-looking.

Benchmarks

I've prepared a small peace of code for testing purposes:

static void Main(string[] args)
{
    Func<int,int > del = (i) => 100 + i;
    var fastDel = new FastInvokeDelegate<int >(() => del(100));

    Stopwatch stopWatch = new Stopwatch();

    var asyncResults = new List<iasyncresult >(10000);

    stopWatch.Start();
    for (int i = 0; i < 10000; i++)
    {
        asyncResults.Add(del.BeginInvoke(i, null, null));
    }
    stopWatch.Stop();
    Console.WriteLine("Delegate.BeginInvoke(): {0}", stopWatch.ElapsedTicks);

    Thread.Sleep(10000);

    stopWatch.Reset();
    stopWatch.Start();
    for (int i = 0; i < 10000; i++)
    {
        del.EndInvoke(asyncResults[i]);
    }
    stopWatch.Stop();
    Console.WriteLine("Delegate.EndInvoke(): {0}", stopWatch.ElapsedTicks);

    asyncResults = new List<iasyncresult >(10000);
    GC.Collect();
    
    stopWatch.Reset();
    stopWatch.Start();
    for (int i = 0; i < 10000; i++)
    {
        asyncResults.Add(fastDel.BeginInvokeFast(null, null));
    }
    stopWatch.Stop();
    Console.WriteLine("FastInvokeDelegate.BeginInvoke(): {0}", stopWatch.ElapsedTicks);
    
    Thread.Sleep(10000);
    
    stopWatch.Reset();
    stopWatch.Start();
    for (int i = 0; i < 10000; i++)
    {
        var res = fastDel.EndInvokeFast(asyncResults[i]);    
    }
    stopWatch.Stop();
    Console.WriteLine("FastInvokeDelegate.EndInvoke(): {0}", stopWatch.ElapsedTicks);
}

Here's the output:

FastAsyncDelegates

Now you can see, why I do call my implementation "fast"...

Implementation

Everything is pretty simple. No cheats with IL, no function pointers. All I did is created a simple regular .NET delegate and decorated it with two extension methods - BeginInvokeFastand EndEnvokeFast. That's the story. The most interesting part here would be IAsyncResultimplementation which does not initialize ManualResetEventuntil it is needed.

public delegate T FastInvokeDelegate<t>();
     
public static class DelegateExtensions
{
    public static IAsyncResult BeginInvokeFast<t>
	(this FastInvokeDelegate<t> del, object state, AsyncCallback callback)
    {
        return new FastInvokeAsyncResult<t>(del, callback, state);
    }
    
    public static T EndInvokeFast<t>(this FastInvokeDelegate<t> del, 
					IAsyncResult asyncResult)
    {
        var result = asyncResult as FastInvokeAsyncResult<t>;
        if (result == null)
            throw new InvalidOperationException("Wrong async result");
        return result.End();
    }
    
    private class FastInvokeAsyncResult<t> : IAsyncResult
    {
        private volatile int m_isCompleted; // 0==not complete, 1==complete.
        private ManualResetEvent m_asyncWaitHandle;
        private volatile int m_asyncWaitHandleNeeded = 0; //0 - is not needed, 1 - needed
        private readonly AsyncCallback m_callback;
        private readonly object m_asyncState;
        // To hold the results, exceptional or ordinary.
        private Exception m_exception;
        private T m_result;
    
        public FastInvokeAsyncResult(FastInvokeDelegate<t> work, 
				AsyncCallback callback, object state)
        {
            m_callback = callback;
            m_asyncState = state;
     
            Run(work);
        }
    
        public bool IsCompleted
        {
            get { return (m_isCompleted == 1); }
        }
        public bool CompletedSynchronously
        {
            get { return false; }
        }
        public WaitHandle AsyncWaitHandle
        {
            get
            {
                if (m_asyncWaitHandleNeeded == 1)
                {
                    return m_asyncWaitHandle;
                }
                m_asyncWaitHandleNeeded = 1;
                m_asyncWaitHandle = new ManualResetEvent(m_isCompleted == 1);
    
                return m_asyncWaitHandle;
            }
        }

        public object AsyncState
        {
            get { return m_asyncState; }
        }
        
        private void Run(FastInvokeDelegate<t> work)
        {
            ThreadPool.QueueUserWorkItem(delegate
            {
                try
                {
                    m_result = work();
                }
                catch (Exception e)
                {
                    m_exception = e;
                }
                finally
                {
                    m_isCompleted = 1;
                    if (m_asyncWaitHandleNeeded == 1)
                    {
                        m_asyncWaitHandle.Set();
                    }
                    if (m_callback != null)
                        m_callback(this);
                }
            });
        }
        
        public T End()
        {
            if (m_isCompleted == 0)
            {
                AsyncWaitHandle.WaitOne();
                AsyncWaitHandle.Close();
            }
            
            if (m_exception != null)
                throw m_exception;
            return m_result;
        }
    }
}

Conclusion

Use cases for FastBeginInvoke/FastEndInvokeare exactly the same as for BeginInvoke/EndInvoke. The only difference is performance.

History

  • 1st July, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Vitaliy Liptchinsky
Technical Lead bwin Interactive Entertainment AG
Austria Austria
The views expressed in my articles are mine and do not necessarily reflect the views of my employer. if(youWantToContactMe) { SendMessage(string.Format("{0}@{1}.com", "liptchinski_vit", "yahoo")); } More info in my LinkedIn profile: http://www.linkedin.com/in/vitaliyliptchinsky

You may also be interested in...

Comments and Discussions

 
GeneralNice article Pin
gglaze7-Jul-09 2:39
membergglaze7-Jul-09 2:39 
GeneralRe: Nice article Pin
Vitaliy Liptchinsky7-Jul-09 2:49
memberVitaliy Liptchinsky7-Jul-09 2:49 
GeneralMy vote of 2 Pin
_FleX1-Jul-09 19:54
member_FleX1-Jul-09 19:54 
GeneralRe: My vote of 2 Pin
Vitaliy Liptchinsky1-Jul-09 22:14
memberVitaliy Liptchinsky1-Jul-09 22:14 
QuestionWhat is the point of callind EndInvoke multiple times? Pin
Henrique F1-Jul-09 13:02
memberHenrique F1-Jul-09 13:02 
AnswerRe: What is the point of callind EndInvoke multiple times? Pin
Vitaliy Liptchinsky1-Jul-09 22:10
memberVitaliy Liptchinsky1-Jul-09 22:10 
GeneralDefine fast Pin
Ramon Smits1-Jul-09 5:36
memberRamon Smits1-Jul-09 5:36 
GeneralRe: Define fast Pin
Ramon Smits1-Jul-09 5:47
memberRamon Smits1-Jul-09 5:47 
GeneralRe: Define fast Pin
Vitaliy Liptchinsky1-Jul-09 5:51
memberVitaliy Liptchinsky1-Jul-09 5:51 
GeneralRe: Define fast Pin
Vitaliy Liptchinsky1-Jul-09 5:48
memberVitaliy Liptchinsky1-Jul-09 5:48 
GeneralRe: Define fast Pin
Mike Marynowski7-Jul-09 4:08
memberMike Marynowski7-Jul-09 4:08 
GeneralRe: Define fast Pin
gglaze7-Jul-09 2:36
membergglaze7-Jul-09 2:36 
QuestionDesign choices Pin
Timothy Bussmann1-Jul-09 5:28
memberTimothy Bussmann1-Jul-09 5:28 
AnswerRe: Design choices Pin
Vitaliy Liptchinsky1-Jul-09 5:41
memberVitaliy Liptchinsky1-Jul-09 5:41 
Hello Timothy, Thank you.
Timothy Bussmann wrote:
What can the standard CLR implementation do that your implementation does not do? You mentioned the remoting framework, but can you be more specific? Just trying to figure out where I can use this code, and where I would have to use the standard .NET implementation.
I've tried recently to find more details/source code of BeginInvoke/EndInvoke methods, but unfortunately didn't succeed. All I know is that both methods are using remoting infrastructure (check our Concurrent Programming on Windows book by Joe Duffy). Actually, you can use this code whenever you can use ThreadPool, but need a result of async operation.
Timothy Bussmann wrote:
I see that you use volatile ints to represent bools. Does it yield better performance?
Actually, you can't declare volatile bools. That's the only reason, I've used ints.
Timothy Bussmann wrote:
Also, I didn't see a type parameter declaration for T, can you clarify that this simply a generic type with no constraints?
Yes, T is generic type.
Timothy Bussmann wrote:
Finally, did you consider creating extension methods on the basic Delegate type, rather than creating your own type?
Yes, I've considered creating extension methods for MulticastDelegate. But in this case you do not have access to the Invoke method, which is provided by the compiler, but only to the DynamicInvoke, which is really slow resulting in poor performance.
Vitaliy Liptchinsky

GeneralRe: Design choices Pin
Timothy Bussmann1-Jul-09 6:32
memberTimothy Bussmann1-Jul-09 6:32 
GeneralRe: Design choices Pin
Vitaliy Liptchinsky1-Jul-09 10:07
memberVitaliy Liptchinsky1-Jul-09 10:07 
GeneralRe: Design choices Pin
katrash19-Aug-09 18:05
memberkatrash19-Aug-09 18:05 
GeneralRe: Design choices Pin
Vitaliy Liptchinsky21-Aug-09 20:55
memberVitaliy Liptchinsky21-Aug-09 20:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150804.3 | Last Updated 1 Jul 2009
Article Copyright 2009 by Vitaliy Liptchinsky
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid