Multi-Threading (4), Delegate based Asynchronous Programming Model

This is a series of articles about Multi-threading.

Introduction

The article, Multi-Threading (2), Implementation Overview, gave three different implementation models for asynchronous programming. This article will discuss APM with examples.

An asynchronous Programming Model (APM) is a native or raw asynchronous programming model using a delegate to create a thread. such that you define a delegate with the same signature as the method you want to call, the common language runtime automatically defines BeginInvoke and EndInvoke methods for this delegate, with the appropriate signatures. Therefore we could say APM is a delegate-based Asynchronous Programming model, as we used as the article name.

Content of the article

  • Introduction
  • EndInvoke: Waiting for an Asynchronous Call with EndInvoke
  • WaitHandle: Waiting for an Asynchronous Call with WaitHandle
  • Poll: Polling for Asynchronous Call Completion
  • Call back: Executing a Callback Method When an Asynchronous Call Completes
  • Summary

EndInvoke: Waiting for an Asynchronous Call with EndInvoke

Pattern

  • Call   to start a new async thread;
  • Call  to end the async calling thread.

EndInvoke might block the calling thread because it does not return until the asynchronous call completes. This is a good technique to use with file or network operations.

Create a delegate: AsyncMethodCaller.

// The delegate must have the same signature as the method
// it will call asynchronously.
public delegate string AsyncMethodCaller(int callDuration, out int threadId);

Create a Test Method, AsyncDemo, and make a test program.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

// https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/calling-synchronous-methods-asynchronously
// Modified, https://www.c-sharpcorner.com/article/multi-threading-4-delegate-based-asynchorous-programming-model/

namespace Async_Await
{
    class Multi_Await6
    {
        // if not shown in Start up object, use this non async main
        public static void Main(string[] args)
        {
            // The asynchronous method puts the thread id here.
            int threadId;

            // Create an instance of the test class.
            AsyncDemo ad = new AsyncDemo();

            // Create the delegate.
            AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

            ConsoleWriteLine($"Line {27}");

            // Initiate the asynchronous call.
            IAsyncResult result = caller.BeginInvoke(3000,
                out threadId, null, null);

            Thread.Sleep(0);

            ConsoleWriteLine($"Line {35}");

            // Call EndInvoke to wait for the asynchronous call to complete,
            // and to retrieve the results.
            string returnValue = caller.EndInvoke(out threadId, result);

            ConsoleWriteLine($"Line {41}");

            Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", 
                threadId, returnValue);


            Console.ReadLine();
        }

        public class AsyncDemo
        {
            // The method to be executed asynchronously.
            public string TestMethod(int callDuration, out int threadId)
            {
                Console.WriteLine("Test method begins.");
                ConsoleWriteLine($"Line {56}");
                Thread.Sleep(callDuration);
                threadId = Thread.CurrentThread.ManagedThreadId;
                return String.Format("My call time was {0}.", callDuration.ToString());
            }
        }
        // The delegate must have the same signature as the method
        // it will call asynchronously.
        public delegate string AsyncMethodCaller(int callDuration, out int threadId);

        // Convenient helper to print colorful threadId on console
        static void ConsoleWriteLine(string str)
        {
            int threadId = Thread.CurrentThread.ManagedThreadId;
            //Console.ForegroundColor = threadId == 1 ? ConsoleColor.White : ConsoleColor.Cyan;

            switch (threadId)
            {
                case 1:
                    Console.ForegroundColor = ConsoleColor.White;
                    break;

                case 3:
                    Console.ForegroundColor = ConsoleColor.Green;
                    break;
            }

            Console.WriteLine(
               $"{str}{new string(' ', 26 - str.Length)}   Thread {threadId}");
        }

    }
}

The BeginIvoke method creates a new thread at Line 30; the thread ends at Line 39 by the EndIvoke method.

Run the program and the output.

The method AsyncDemo is run asynchronously, with thread number 3 shown in green at Line 56 in the output.

WaitHandle: Waiting for an Asynchronous Call with WaitHandle

Besides BeginInvoke and EndInvoke, we add AsynWaitHandle.WaitOne() at Line 41, the AsyncWaitHandle is a property of the IAsyncResult that is returned by BeginInvoke. The WaitHandle is signaled when the asynchronous call completes, 

Using a WaitHandle, one can perform additional processing before or after the asynchronous call completes but before calling EndInvoke to retrieve the results. For testing purposes, we also add two loops in the main program and the async method in the light blue frame at Line 35 and Line 68, respectively.

Run the code, and the output is like this, the main thread 1 and the async thread 3 (randomly created from the thread pool) are running at the "same time".

Poll: Polling for Asynchronous Call Completion

We add IAsyncResult.IsCompleted Property between BeginInvoke and EndInvoke: at Line 41:

The AsyncDemo is like this.

Comment out Line 78 in the AsyncDemo method:   to make the poll in the same step as an async method, Run the code we have output.

Polling for completion allows the calling thread to continue executing (Thread 1 in white) while the asynchronous call executes on a ThreadPool thread (Thread 3 in green).

Call Back: Executing a Callback Method When an Asynchronous Call Completes

If the thread that initiates the asynchronous call does not need to be the thread that processes the results, you can execute a callback method when the call completes. The callback method is executed on a ThreadPool thread.

  • Call BeginInvoke from the calling thread to initiate a new thread;
  • Pass a delegate for a callback method to BeginInvoke. The method is executed on a ThreadPool thread when the asynchronous call completes. The callback method calls EndInvoke.

Call back function:

Run the code, output:

Notes. The callback is made on a ThreadPool thread. ThreadPool threads are background threads, which do not keep the application running if the main thread ends, so the main thread of the example has to sleep long enough for the callback to finish. If we comment out Lile 59, the result will be like this.

In fact, in this test program, due to Line 63, the main program is still running, so we can see that Thread 3 will continue to run; without Line 63, the main program will terminate, and the new thread will end, too.

Summary

Basically, there are four ways to implement the APM,

Use BeginInvoke andEndInvoke to make asynchronous calls. After callingBeginInvoke, you can do the following:

  • Call back: Pass a delegate for a callback method to BeginInvoke. The method is executed on a ThreadPool thread when the asynchronous call completes. The callback method calls EndInvoke.
  • EndInvoke: Do some work and then call EndInvoke to block until the call completes.
  • WaitHandle: Obtain a WaitHandle using the IAsyncResult.AsyncWaitHandle property, use its WaitOne method to block execution until the WaitHandle is signaled and then call EndInvoke.
  • Poll: the IAsyncResult returned by BeginInvoke to determine when the asynchronous call has been completed, and then call EndInvoke.

References