Click here to Skip to main content
15,861,172 members
Articles / Programming Languages / C#

.NET Framework Delegates: Understanding Asynchronous Delegates

Rate me:
Please Sign up or sign in to vote.
4.76/5 (15 votes)
27 Aug 2009CPOL11 min read 79.1K   85   13
An article to help clarify delegates and how to use them asynchronously.

Introduction

Delegates are special types supported by the .NET Framework that represent a strongly typed method signature. Delegates can be instantiated and formed over any target method and instance combination where the method matches the method’s signature. C# allows the creation of special classes using the delegate keyword. We call such a class a delegate class. Instances of these delegate classes are called delegate objects. Conceptually, a delegate object is a reference to one or more methods (static or instance). We can then call/invoke a delegate object with the same syntax to call a method. This causes the call to a method. But note that the call to these methods is done by the same thread which called the delegate object. We refer to this as a synchronous call. But when making a synchronous call to a method, the caller thread blocks while the call is active. While the thread is blocked, it could create other threads, despite the fact that the CPU may be idle. So even though these threads might not be consuming CPU, they are wasting resources, which is not cost-effective. When a thread makes an asynchronous call to a method, the call returns immediately. The caller thread is not blocked; it is free to perform some other task. The .NET infrastructure obtains a thread for the method invocation and delivers the in parameters passed by the calling code. The async thread can then run the method in parallel to the calling thread. If the method generates some data and returns this value, the calling thread must be able to access this data. The .NET asynchronous feature supports two mechanisms: that calling thread can either ask for the results, or the infrastructure can deliver the results to the calling thread when the results are ready. The purpose of this article is to explain delegates and how to use them asynchronously.

Delegates

In C#, a new delegate type is created using the delegate keyword:

C#
public delegate void MyDelegate(int x, int y);

This says that we created a new delegate type, named MyDelegate, which can constructed over methods with void return types and that accept two arguments each typed as int. Our delegate can then be formed over a target, passed around, and then invoked at some point in the future. Invocation in C# looks like an ordinary function call:

C#
class Foo
{
   void PrintPair(int a, int b)
   {
     Console.WriteLine("a = {0}", a);
     Console.WriteLine("b = {0}", b);
   }

   void CreateAndInvoke()
   {
     // implied 'new MyDelegate(this.PrintPair)':
     MyDelegate del = PrintPair;
     del(10, 20);
  }
}

Doesn’t make sense yet? CreateAndInvoke constructs a new MyDelegate, formed over the PrintPair method with the current this pointer as the target. The actual IL emitted by the C# compiler shows some of the complexities of delegates in the underlying type system:

C#
struct MyDelegate :  System.MulticastDelegate
{
  public MyDelegate(object target, IntPtr, methodPtr);

  private object target;
  private IntPtr methodPtr;

  public internal void Invoke(int x, int y)
  public internal System.IAsyncResult BeginInvoke(int x, int y, 
                  System.IAsyncCallback callback, object state);
  public internal void EndInvoke(System.IAsyncResult  result);

}

The constructor is used to form a delegate over a target object and a function pointer. The Invoke, BeginInvoke, and EndInvoke methods implement the delegate invocation routine and are marked as internal (i.e., Runtime in IL) to indicate to the CLR that it provides the implementation; their IL bodies are left blank. Invoke performs a synchronous invocation, while the BeginInvoke and EndInvoke functions follow the Asynchronous Programming Model pattern. But, notice first that the MyDelegate type breaks a rule, namely that structs cannot derive from other types other than System.ValueType. Delegates have special support in the Common Type System (CTS), so this is allowed. Also notice that MyDelegate derives from MulticastDelegate; this type is the common base for all delegates created in C#, and supports delegates that have multiple targets. Consider another delegate creation:

C#
public delegate int StringDelegate(string str);

This can be declared in a class or at a global scope. The C# compiler will generate a new class from this declaration and derive from System.MulticastDelegate. Examine the methods of this class and its base class, System.Delegate (again):

C#
public sealed class StringDelegate : System.MulticastDelegate
{
   public StringDelegate (object obj, int method);
   public virtual int Invoke(string str);
   public virtual IAsyncResult BeginInvoke(string str, 
      AsyncCallback asc, object stateObject);
   public virtual int EndInvoke(IAsyncResult result);
}

Now, let’s see some code that correlates to our first delegate creation, MyDelegate:

C#
using System;
public static class App {
   public delegate void MyDelegate(int x, int y);
   public static void PrintPair(int a, int b)
   {
     Console.WriteLine("a = {0}", a);
     Console.WriteLine("b = {0}", b);
   }
   public static void Main()
   {
     // Implied 'new MyDelegate(this.PrintPair)':
     MyDelegate d = PrintPair;
     // Implied 'Invoke':
     d(10, 20);
   }
}

When compiled, it produces this output:

a = 10
b = 20

Inside Delegates

Assume that we have (again) defined our own MyDelegate type, like this in C#:

C#
delegate string MyDelegate(int x);

We now know that this represents a function pointer type that can refer to any method taking a single int argument and returning a string. And, we also know that when working with an instance of this delegate type, we’ll declare variables of type MyDelegate. Furthermore, we know that behind the scenes, the compiler is generating a new class (type) for us:

C#
private sealed class MyDelegate : MulticastDelegate
{
    public extern MyDelegate(object object, IntPtr method);
    public extern virtual string Invoke(int x);
    public extern virtual IAsyncResult BeginInvoke(int x, 
                  AsyncCallback  callback, object   object);
    public extern virtual string Endinvoke((IAsyncResult result);
}

Now, let’s assume that we have our own custom type, MyType, with a method MyFunc whose signature matches MyDelegate’s exactly. Now notice that the parameters are not named identically. This is all right, because delegates only require that the expected types be found in the correct signature’s positions:

C#
class MyType
{
  public string MyFunc(int foo)
  {
     return "MyFunc called with the value  '" + foo + "' foo foo;
  }
}

Once we have a delegate type in the metadata and a target function we’d like to call, we must form an instance of the delegate over a target. This constructs a new instance of the delegate type using the constructor MyDelegate(object, IntPtr). The code passes the target as the first argument and a pointer to the code function as the second. The syntax for this is:

C#
MyType mt = new MyType();
MyDelegate md = mt.MyFunc;

So now, we put the sum of the parts to form the whole:

C#
using System;
delegate string MyDelegate(int x);
class MyType
{
  public string MyFunc(int foo)
  {
     return "MyFunc called with the value '" + foo + "' for foo";
  }
}

public class Program {
  public static void Main()
  {
    MyType mt = new MyType();
    MyDelegate md = mt.MyFunc;
    Console.WriteLine(md.Invoke(5));
    Console.WriteLine(md(5));
  }
}

The code compiles and spits out this result:

MyFunc called with the value '5' for foo
MyFunc called with the value '5' for foo

So What are Asynchronous Delegates?

Before using an asynchronous delegate, it is good to remember that all the delegate types automatically offer two methods named BeginInvoke and EndInvoke. The signatures of these methods are based on the signature of the delegate type which contains them. For example, the following delegate type:

C#
delegate int MyDelegate(int x, int y)

exposes the following methods by compiler generation:

C#
IAsyncResult BeginInvoke(int x, int y, AsyncCallback callback, 
             object object, IAsyncResult result);
int EndInvoke (IAsyncResult result);

These two methods are generated by the compiler. To call a method in an asynchronous way, you must first reference it using a delegate object which has the same signature. Then, you have to call BeginInvoke on this delegate object. As you have seen, the compiler ensures that the first arguments of the BeginInvoke method are the arguments of the method to be called. The last two arguments of this method, IAsyncResult and object, will be discussed shortly. The return value of an asynchronous call can be recovered by calling the EndInvoke method. There also the compiler ensures that the return value of EndInvoke is the same as the return value of the delegate type (this type is an int in our example). The call to EndInvoke is blocking, meaning that the call will only return when the asynchronous execution is done. The following example illustrates the asynchronous call to a ShowSum method:

C#
using System;
using System.Threading;
public class Program {
 public delegate int TheDelegate( int x, int y);
 static int ShowSum( int x, int y ) {
 int sum = x + y;
 Console.WriteLine("Thread #{0}: ShowSum()  Sum = {1}", 
                   Thread.CurrentThread.ManagedThreadId, sum);
 return sum;
}
public static void Main() {
  TheDelegate d = ShowSum;
  IAsyncResult ar = d.BeginInvoke(10, 10, null, null);
  int sum = d.EndInvoke(ar);
  Console.WriteLine("Thread #{0}: Main()     Sum = {1}", 
                    Thread.CurrentThread.ManagedThreadId, sum);
  }
}

Output:

Thread #3: ShowSum()  Sum = 20
Thread #1: Main()     Sum = 20

The BeginInvoke method has a parameter for each parameter of the underlying delegate (just like Invoke) and adds two parameters: an IAsyncCallback delegate, which gets invoked when the asynchronous operation completes, and an object, which is passed as the IAsyncResult.AsyncState property value to the callback function. The method returns an IAsyncResult, which can be used to monitor completion, wait on the WaitHandle, or complete the asynchronous call.

C#
public interface IAsyncResult
{
    // Properties
    object AsyncState { get; }
    WaitHandle AsyncWaitHandle { get; }
    bool CompletedSynchronously { get; }
    bool IsCompleted { get; }
}

When the delegate has completed execution, you must call EndInvoke on the delegate, passing in the IAsyncResult. This cleans up the WaitHandle (if it was allocated), throws an exception if the delegate failed to execute correctly, and has a return type matching the underlying method’s. It returns the value returned by the delegate invocation:

C#
using System;
public sealed class Program {
  delegate int IntIntDelegate(int x);
  private static int Square(int x) { return x * x; }
  private static void AsyncDelegateCallback(IAsyncResult ar)
  {
    IntIntDelegate f = (IntIntDelegate)ar.AsyncState;
    Console.WriteLine(f.EndInvoke(ar));
  }

  public static void Main()
  {
    IntIntDelegate f = Square;

    /* Version 1: Spin wait (quick delegate method) */
    IAsyncResult ar1 = f.BeginInvoke(10, null, null);
    while (!ar1.IsCompleted)
        // Do some expensive work while it executes.
    Console.WriteLine(f.EndInvoke(ar1));

    /* Version 2: WaitHandle wait (longer delegate method) */
    IAsyncResult ar2 = f.BeginInvoke(20, null, null);
    // Do some work.
    ar2.AsyncWaitHandle.WaitOne();
    Console.WriteLine(f.EndInvoke(ar2));

    /* Version 3: Callback approach */
    IAsyncResult ar3 = f.BeginInvoke(30, AsyncDelegateCallback, f);
    // We return from the method (while the delegate executes).
  }
}

Output:

100
400

Now, if the method has parameters that are reference types and are passed in as parameters, then the asynchronous method will be able to call methods on the reference types that change its state. You can use this to return values from the async method. The code below shows an example of this. The GetData delegate takes an object of type System.Array as an in parameter. Since this passed in by reference, this method can change the values in the array. However, since the object can be accessed by two threads, you have to make sure that you do not access this shared object until the asynchronous method has completed:

C#
using System;
class App
{
   delegate void GetData(byte[] b);
   static void GetBuf(byte[] b)
   {
      for (byte x = 0; x < b.Length; x++)
         b[x] = (byte)(x*x);
   }
   static void Main()
   {
      GetData d = new GetData(App.GetBuf);
      byte[] b = new byte[10];
      IAsyncResult ar;
      ar = d.BeginInvoke(b, null, null);
      ar.AsyncWaitHandle.WaitOne();
      for (int x = 0; x < b.Length; x++)
         Console.Write("{0} ", b[x]);
   }
}

Output:

0
1
4
9
16
25
36
49
64
81
100

To use a callback method, you need to reference it with a delegate object of type System.AsyncCallback passed as the next to the last argument to the BeginInvoke method. This method must conform itself to the delegate type, which means it must have a return type of void (in the case of the example below) and take a single argument of type IAsyncResult:

C#
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;
class Program {
  public delegate int MyDelegate(int x, int y);
  static AutoResetEvent e = new AutoResetEvent(false);
  static int WriteSum( int x, int y) {
  Console.WriteLine("Thread# {0}: Sum = {1}", 
          Thread.CurrentThread.ManagedThreadId, x + y);
  return x + y;
}

static void SumDone(IAsyncResult async) {
  Thread.Sleep( 1000 );
  // AsyncResult of the System.Runtime.Remoting.Messaging namepsace
  MyDelegate func = ((AsyncResult) async).AsyncDelegate as MyDelegate;
  int sum = func.EndInvoke(async);
  Console.WriteLine("Thread# {0}: Callback method sum = {1}", 
                    Thread.CurrentThread.ManagedThreadId, sum);
  e.Set();
}

static void Main() {
  MyDelegate func = WriteSum;

  // the C# 2.0 compiler infer a delegate object of type
  // AsyncCallback to reference the SumDone() method
  IAsyncResult async = func.BeginInvoke(10, 10, SumDone, null);
  Console.WriteLine("Thread# {0}: BeginInvoke() called! Wait for SumDone() completion.", 
                    Thread.CurrentThread.ManagedThreadId);
  e.WaitOne();
  Console.WriteLine("Thread# {0}: Bye....", 
                    Thread.CurrentThread.ManagedThreadId);
  }
}

Compiling this code results in the following:

Thread# 1: BeginInvoke() called! Wait for SumDone() completion.
Thread# 3: Sum = 20
Thread# 3: Callback method sum = 20
Thread# 1: Bye....

An Important Note about control.BeginInvoke vs. delegate.BeginInvoke

This article used C# 2.0 syntax and therefore had is its basis on writing delegate code on the .NET Framework 2.0 platform. The reason why I state this is that often in technical documentation, the topic of delegates will be coupled with, or have relation to, events. An event is a message sent by an object (a control) to signal the occurrence of an action. The action could be caused by some user interaction, such as a mouse click, or it could be triggered by some other program logic. The object that raises the event is called the event sender. The object that captures the event and responds to it called the event receiver. In event communication, the event sender class does not know which object or method will receive (handle) the event it raises. What is needed is an intermediary (or pointer-like mechanism) between the source and the receiver. A special type called a delegate provides the functionality of a function pointer. A delegate is a class that can hold a reference to a method. As has been stated, a delegate class has a signature, and can hold references only to methods that match its signature. When writing code for Windows Forms, the following example show an event delegate declaration:

public delegate void AlarmEventHandler( object sender, EventArgs e);

So while we examined how to use delegates with the goal of understanding how to use them asynchronously, we also should clarify any other usage of delegates (in this case, a Windows Forms UI). The standard signature of an event handler delegate defines a method that does not return a value, who first parameter is of type Object and refers to the instance that raises the event, and whose second parameter is derived from type EventArgs and holds that event data. EventHandler is a predefined delegate that specifically represents an event handler method for an event that does not generate data. To associate the event with the method that will handle the event, add an instance of the delegate to the event. The event handler is called whenever the event occurs, unless you remove the delegate. Having said that said, let’s look briefly at multithreading and GUI applications.

If you have been involved with Win32 development, then you would know that it is typical to access APIs synchronously; a thread initiates some task, then waits patiently for the task to complete. If the code reaches a more advanced level, it could create a worker thread to make this synchronous call, freezing the main thread to continue its work. Using worker threads to perform length blocking calls crucial for GUI applications because the blocking thread that pumps the message queue disables the UI of the application. The process of creating worker threads like this is never straightforward. In short, thread creation is expensive. Creating a new worker thread every time you need to make a blocking call could result in more threads than are necessary, increasing resource consumption. In .NET, asynchronous execution can be a valuable design technique. For example, you should use asynchronous execution in a Windows® Forms application when you want to execute a long-running command without blocking the responsiveness of the user interface. As you will see, programming with delegates makes it relatively easy to run a command asynchronously on a secondary thread. That means you can build a Windows Forms application that executes long-running calls across the network without freezing the user interface. Herein lies the issue of control.BeginInvoke vs. delegate.BeginInvoke. When you call Control.BeginInvoke, the call to Control.Invoke is made on a threadpool thread and the call to BeginInvoke returns immediately. So, in effect, you have a worker thread calling BeginInvoke. The threadpool thread then takes over and calls Control.Invoke, waiting on the return value. The delegate passed to Invoke is then invoked on the UI thread. If you call Delegate.BeginInvoke, then you go from your worker thread to the threadpool thread, where the method pointed to by the delegate gets executed. If you are accessing UI elements on this thread, you are going to get unpredictable results. To call Control.BeginInvoke with an end goal of having a matching EndInvoke just allows the processing to continue on the thread that called them, not waiting for the UI to update. Having examined the differences between delegate's BeginInvoke and Control.BeginInvoke, it appears that Control's BeginInvoke() ensures that the delegate is invoked in the thread that created the Control's context. This implies no thread pool threads are involved. This was illustrated by a .NET MVP's test application.

Suggested Reading

  • "Professional .NET Framework 2.0" by Joe Duffy.
  • "The CLR via C#", by Jeffrey Richter

License

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


Written By
Software Developer Monroe Community
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
PraiseGreat Pin
yuzaihuan29-Mar-18 20:05
yuzaihuan29-Mar-18 20:05 
Questioncompiler is generating a new class (type) for us Pin
pramodkumarw16-May-13 1:23
pramodkumarw16-May-13 1:23 
QuestionThanks Pin
Hua Yujun3-Dec-12 20:58
Hua Yujun3-Dec-12 20:58 
GeneralMy vote of 5 Pin
AnirbanGhatak1-Feb-12 19:30
AnirbanGhatak1-Feb-12 19:30 
QuestionDelegate as a struct? Pin
sid_boss19-Jan-12 9:56
sid_boss19-Jan-12 9:56 
Generalexcellent revision ! Pin
BillWoodruff29-Aug-09 22:08
professionalBillWoodruff29-Aug-09 22:08 
Generalgood article and one small suggestion Pin
BillWoodruff26-Aug-09 19:15
professionalBillWoodruff26-Aug-09 19:15 
GeneralRe: good article and one small suggestion Pin
logicchild27-Aug-09 14:40
professionallogicchild27-Aug-09 14:40 
GeneralMight be good to clarify distinction between delegate.BeginInvoke and control.BeginInvoke Pin
supercat926-Aug-09 5:29
supercat926-Aug-09 5:29 
GeneralRe: Might be good to clarify distinction between delegate.BeginInvoke and control.BeginInvoke Pin
BillWoodruff26-Aug-09 19:09
professionalBillWoodruff26-Aug-09 19:09 
GeneralRe: Might be good to clarify distinction between delegate.BeginInvoke and control.BeginInvoke Pin
supercat927-Aug-09 5:13
supercat927-Aug-09 5:13 
GeneralRe: Might be good to clarify distinction between delegate.BeginInvoke and control.BeginInvoke Pin
logicchild27-Aug-09 14:37
professionallogicchild27-Aug-09 14:37 
Hi supercat9. Point taken. I will adjust the article accordingly.
GeneralRe: Might be good to clarify distinction between delegate.BeginInvoke and control.BeginInvoke Pin
supercat928-Aug-09 6:21
supercat928-Aug-09 6:21 

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

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