Calling Synchronous Methods Asynchronously with different Patterns

There are several patterns to call a synchronous method asynchronously as shown below.

1- BeginInvoke and EndInvoke
2- WaitHandle
3- Polling
4- Callback

 

BeginInvoke and EndInvoke Pattern

As you see that first message written in console is worked in main thread (ThreadID is 1), But, DoWork() method is called asynchronously, so that, second message is worked on different thread (ThreadID is 3).

DoWork() method returns int value and without parameters, so that, BeginInvoke method shows two parameters(callback, @object) as shown in Figure-2

Notice that EndInvoke() method takes asyncResult variable returned by BeginInvoke() method and it also returns int value because of Func delegate in Figure-3.

Figure-1

Figure-2

Figure-3

using System;
using System.Threading;

namespace AsyncBeginEndInvokePattern1
{
    class Program
    {
        static void Main(string[] args)
        {
            Func funcDelegate = DoWork;

            IAsyncResult asyncResult = funcDelegate.BeginInvoke(null, null);

            Console.WriteLine($"Main thread {Thread.CurrentThread.ManagedThreadId} does some work.");

            Thread.Sleep(TimeSpan.FromSeconds(2));

            int result = funcDelegate.EndInvoke(asyncResult);
        }

        static int DoWork()
        {
            Console.WriteLine($"Test method begins in thread {Thread.CurrentThread.ManagedThreadId}");

            Thread.Sleep(TimeSpan.FromSeconds(6));

            return 45;
        }
    }
}

If method has some parameters, first parameters of BeginInvoke() method will be same as shown in Figure-5.
Notice that DoWork() method is not used directly, it is encapsulated with a delegate. I used
Func<int, int, int> delegate which is signature of DoWork() method in this code.
Let’s examine declaration of DoWork() method below. you see that there are two int parameters and returns int type.

int DoWork(int num1, int num2) is equals to Func<int, int, int>

Figure-4

Figure-5

using System;
using System.Threading;

namespace AsyncBeginEndInvokePattern2
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<int, int, int> funcDelegate = DoWork;

            IAsyncResult asyncResult = funcDelegate.BeginInvoke(30, 20, null, null);

            Console.WriteLine($"Main thread {Thread.CurrentThread.ManagedThreadId} does some work.");

            Thread.Sleep(TimeSpan.FromSeconds(2));

            int result = funcDelegate.EndInvoke(asyncResult);
        }

        static int DoWork(int num1, int num2)
        {
            Console.WriteLine($"Test method begins in thread {Thread.CurrentThread.ManagedThreadId}");

            Thread.Sleep(TimeSpan.FromSeconds(6));

            return num1 + num2;
        }
    }
}

 

Let’s use our delegate MethodCaller

 

using System;
using System.Threading;

namespace AsyncBeginEndInvokePattern3
{
    class Program
    {
        //1. Way
        delegate TResult MethodCaller(T1 arg1, T2 arg2);

        //2. Way
        //delegate TResult MethodCaller<T1, T2, TResult>(T1 arg1, T2 arg2);

        static void Main(string[] args)
        {
            MethodCaller<int, int, int> funcDelegate = DoWork;

            IAsyncResult asyncResult = funcDelegate.BeginInvoke(30, 20, null, null);

            Console.WriteLine($"Main thread {Thread.CurrentThread.ManagedThreadId} does some work.");

            Thread.Sleep(TimeSpan.FromSeconds(2));

            int result = funcDelegate.EndInvoke(asyncResult);

            Console.WriteLine($"DoWork(30, 20) returned {result}");
        }

        static int DoWork(int num1, int num2)
        {
            Console.WriteLine($"Test method begins in thread {Thread.CurrentThread.ManagedThreadId}");

            Thread.Sleep(TimeSpan.FromSeconds(6));

            return num1 + num2;
        }
    }
}

 

Let’s see how to use anonymous delegate asynchronously.

 

using System;
using System.Threading;

namespace AsyncBeginEndInvokePattern4
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<int, int, int> funcDelegate = delegate(int num1, int num2)
            {
                Console.WriteLine($"Test method begins in thread {Thread.CurrentThread.ManagedThreadId}");

                Thread.Sleep(TimeSpan.FromSeconds(6));

                return num1 + num2;
            };

            IAsyncResult asyncResult = funcDelegate.BeginInvoke(30, 20, null, null);

            Console.WriteLine($"Main thread {Thread.CurrentThread.ManagedThreadId} does some work.");

            Thread.Sleep(TimeSpan.FromSeconds(2));

            int result = funcDelegate.EndInvoke(asyncResult);

            Console.WriteLine($"DoWork(30, 20) returned {result}");
        }
    }
}

 

I will always use anonymous delegates in examples from now on. Below code will also show using delegate with lambda notation.

 

using System;
using System.Threading;

namespace AsyncBeginEndInvokePattern5
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<int, int, int> funcDelegate = (int num1, int num2) =>
            {
                Console.WriteLine($"Test method begins in thread {Thread.CurrentThread.ManagedThreadId}");

                Thread.Sleep(TimeSpan.FromSeconds(6));

                return num1 + num2;
            };

            IAsyncResult asyncResult = funcDelegate.BeginInvoke(30, 20, null, null);

            Console.WriteLine($"Main thread {Thread.CurrentThread.ManagedThreadId} does some work.");

            Thread.Sleep(TimeSpan.FromSeconds(2));

            int result = funcDelegate.EndInvoke(asyncResult);

            Console.WriteLine($"DoWork(30, 20) returned {result}");
        }
    }
}

WaitHandle Pattern

You can perform additional processing before or after the asynchronous call complete using WaitHandle.
 

using System;
using System.Threading;

namespace AsyncBeginEndInvokePattern6
{
    class Program
    {
        static void Main(string[] args)
        {
            Func funcDelegate = (int num1, int num2) =>
            {
                Console.WriteLine($"Test method begins in thread {Thread.CurrentThread.ManagedThreadId}");

                Thread.Sleep(TimeSpan.FromSeconds(6));

                return num1 + num2;
            };

            IAsyncResult asyncResult = funcDelegate.BeginInvoke(30, 20, null, null);

            Console.WriteLine($"Main thread {Thread.CurrentThread.ManagedThreadId} does some work.");

            Thread.Sleep(TimeSpan.FromSeconds(2));

            //Wait main thread for the child thread
            asyncResult.AsyncWaitHandle.WaitOne();

            Console.WriteLine("You can do other operations");

            asyncResult.AsyncWaitHandle.Close();

            //This wil return result without delay. Because, WaitOne() lets finishing child operation.
            int result = funcDelegate.EndInvoke(asyncResult);

            Console.WriteLine($"DoWork(30, 20) returned {result}");
        }
    }
}

Polling Pattern

Use Polling for completion allows the calling thread to continue executing while the asynchronous call executes on a ThreadPool thread.
 

using System;
using System.Threading;

namespace AsyncBeginEndInvokePattern7
{
    class Program
    {
        static void Main(string[] args)
        {
            Func funcDelegate = (int num1, int num2) =>
            {
                Console.WriteLine($"Test method begins in thread {Thread.CurrentThread.ManagedThreadId}");

                Thread.Sleep(TimeSpan.FromSeconds(6));

                return num1 + num2;
            };

            IAsyncResult asyncResult = funcDelegate.BeginInvoke(30, 20, null, null);

            Console.WriteLine($"Main thread {Thread.CurrentThread.ManagedThreadId} does some work.\n");

            while (!asyncResult.IsCompleted)
            {
                Thread.Sleep(250);

                Console.Write(".");
            }

            //This wil return result without delay. Because, WaitOne() lets finishing child operation.
            int result = funcDelegate.EndInvoke(asyncResult);

            Console.WriteLine($"\nDoWork(30, 20) returned {result}");
        }
    }
}

Leave a Reply