C#Asynchronous Programming 1 APM Mode Asynchronous Programming Development

Posted by JeremyTiki on Sun, 26 May 2019 19:15:08 +0200

C#has more than 10 years of history. From the update progress of Microsoft version 1 of 2 years alone, it is extremely vigorous. Asynchronous programming in C#has also undergone several versions of evolution. From today on, a series of blogs have been written to record the development of asynchronous programming in C#Advertisement for a friend who likes my article, please click on "Pay attention to me" below.Thank you

I contacted and used C# in 2004, when C# was version 1.1, so we started talking about that.At that time, I looked at writing programs in the University by myself. Most of the programs I wrote were synchronous programs, starting up at most one thread.....In fact, in the era of C#1.1, there was already a complete asynchronous programming solution, APM (Asynchronous Programming Model).If you still don't know "synchronous program, asynchronous program", please Baidu by yourself.

The most representative feature of the APM asynchronous programming model is that an asynchronous function consists of two methods starting with Begin and ending.Begin begins with a method that initiates execution of the asynchronous function, and End starts with a method that waits for execution of the asynchronous function to end and returns the result of execution.Here is an implementation of the simulation (a standard APM model asynchronous implementation will be written later):

public class Worker
    {
        public int A { get; set; }
        public int B { get; set; }
        private int R { get; set; }
        ManualResetEvent et;
        public void BeginWork(Action action)
        {
            et = new ManualResetEvent(false);
            new Thread(() =>
            {
                R = A + B;
                Thread.Sleep(1000);
                et.Set();
                if(null != action)
                {
                    action();
                }
            }).Start();
        }

        public int EndWork()
        {
            if(null == et)
            {
                throw new Exception("call EndWork Before, you need to call BeginWork");
            }
            else
            {
                et.WaitOne();
                return R;
            }

        } 
    }
        static void Main(string[] args)
        {
           Worker w = new Worker();
            w.BeginWork(()=> {
                Console.WriteLine("Thread Id:{0},Count:{1}", Thread.CurrentThread.ManagedThreadId,
                    w.EndWork());
            });
            Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);
            Console.ReadLine();
        }

In the simulated APM model above we used Thread, ManualResetEvent. If you are unfamiliar with multithreading and ManualResetEvent, please read my previous Blog. C#Multithreaded Usage Series - Interthread Collaboration ManualResetEvent "."Asynchronous programming in C#inevitably involves multithreaded knowledge. Although Microsoft does a lot of encapsulation in the Framework, friends should understand its nature.

The simplicity of the APM asynchronous model simulated above is due to the introduction of many excellent grammar rules during the development of C#.The example above uses Lambda expressions a lot. If you are unfamiliar with anonymous delegates and lambda expressions, you can see my previous Bolg. Anonymous delegation and Lambda expression "."With so many advertisements above, let's take a look at how the standard APM model implements asynchronous programming.

IAsyncResult interface

The IAsyncResult interface defines the state of the asynchronous function, and its properties and meanings are as follows:

   //     Represents the state of an asynchronous operation.
    [ComVisible(true)]
    public interface IAsyncResult
    {
        //
        // abstract:
        //     Gets a value indicating whether the asynchronous operation has been completed.
        //
        // Return results:
        //     If the operation is completed, then true;Otherwise it is false. 
        bool IsCompleted { get; }
        //
        // abstract:
        //     Gets the System.Threading.WaitHandle. 
        //
        // Return results:
        //     Used to wait for an asynchronous operation to complete System.Threading.WaitHandle. 
        WaitHandle AsyncWaitHandle { get; }
        //
        // abstract:
        //     Gets a user-defined object that qualifies or contains information about asynchronous operations.
        //
        // Return results:
        //     A user-defined object that qualifies or contains information about asynchronous operations.
        object AsyncState { get; }
        //
        // abstract:
        //     Gets a value indicating whether the asynchronous operation is completed synchronously.
        //
        // Return results:
        //     If the asynchronous operation completes synchronously, true;Otherwise it is false. 
        bool CompletedSynchronously { get; }
    }
Note: ManualResetEvent in Model Example 1 inherits from WaitHandle
APM legend implementation
After understanding the IAsyncResult interface, we will complete the rewriting of the simulation example by implementing the IAsyncResult interface as follows:
    public class NewWorker
    {
        public class WorkerAsyncResult : IAsyncResult
        {
            AsyncCallback callback;
            public WorkerAsyncResult(int a,int b, AsyncCallback callback, object asyncState) {
                A = a;
                B = b;
                state = asyncState;
                this.callback = callback;
                new Thread(Count).Start(this);
            }
            public int A { get; set; }
            public int B { get; set; }

            public int R { get; private set; }

            private object state;
            public object AsyncState
            {
                get
                {
                    return state;
                }
            }
            private ManualResetEvent waitHandle;
            public WaitHandle AsyncWaitHandle
            {
                get
                {
                    if (null == waitHandle)
                    {
                        waitHandle = new ManualResetEvent(false);
                    }
                    return waitHandle;
                }
            }
            private bool completedSynchronously;
            public bool CompletedSynchronously
            {
                get
                {
                    return completedSynchronously;
                }
            }
            private bool isCompleted;
            public bool IsCompleted
            {
                get
                {
                    return isCompleted;
                }
            }
            private static void Count(object state)
            {
                var result = state as WorkerAsyncResult;
                result.R = result.A + result.B;
                Thread.Sleep(1000);
                result.completedSynchronously = false;
                result.isCompleted = true;
                ((ManualResetEvent)result.AsyncWaitHandle).Set();
                if (result.callback != null)
                {
                    result.callback(result);
                }
            }
        }
        public int Num1 { get; set; }
        public int Num2 { get; set; }

        public IAsyncResult BeginWork(AsyncCallback userCallback, object asyncState)
        {
            IAsyncResult result = new WorkerAsyncResult(Num1,Num2,userCallback, asyncState);
            return result;
        }

        public int EndWork(IAsyncResult result)
        {
            WorkerAsyncResult r = result as WorkerAsyncResult;
            r.AsyncWaitHandle.WaitOne();
            return r.R;
        }
    }

Sample Code Analysis:

The NewWorker's internal class, WorkerAsyncResult, in the code above is the key point, which implements the IAsyncResult interface and is responsible for opening a new thread to complete the calculation.

In WorkerAsyncResult, two private attributes, A and B, are added to store the values used for calculation, and an external readable, non-writable attribute, R, is used to store the results of the internal operation of WorkerAsyncResult.The AsyncWaitHandle property is acted on by the ManualResetEvent and the ManualResetEvent is created (but not released) on the first visit.Other interface properties are implemented properly and there is nothing to say.

A new static Count method is added to WorkerAsyncResult with the parameter state as the current WorkerAsyncResult object that calls the Count method.The Count method runs in the newly started thread of the WorkerAsyncResult object, so Thread.Sleep(1000) will block the new thread for one second.Then set whether the current WorkerAsyncResult object completes synchronously to false and asynchronously to true, release the ManualResetEvent notification to wait for the thread to get the notification to enter the execution state, determine if there is an asynchronous execution end callback delegate, and call back if there is one.

NewWorker is very simple, and the Num1 and Num2 attributes are the values to be calculated.BeginWork creates a WorkerAsyncResult object and passes two numeric values, Num1, Num2, userCallback callback delegate, and asyncState of object type, to the WorkerAsyncResult object to be created.With this step, the WorkerAsyncResult object obtains all the data required for the operation, callbacks after the operation is completed, and immediately starts a new thread for the operation (executing the WorkerAsyncResult.Count method).

Because WorkerAsyncResult.Count executes in a new thread, the state of the new thread cannot be accurately known outside the thread.To satisfy the need for external threads to synchronize with new threads, add the EndWork method in NewWorker with an IAsyncResult parameter type.To call the EndWork method, pass in the WorkerAsyncResult object obtained by BeginWork. After the EndWork method obtains the WorkerAsyncResult object, call the WorkerAsyncResult.AsyncWaitHandle.WaitOne() method, wait for the ManualResetEvent notification, and when the notification is obtained, the operation thread is finished (the thread is not finished), and get the operation result R and return.

Next comes the NewWorker caller, as follows:

        static void Main(string[] args)
        {
            NewWorker w2 = new NewWorker();
            w2.Num1 = 10;
            w2.Num2 = 12;
            IAsyncResult r = null;
            r = w2.BeginWork((obj) => {
            Console.WriteLine("Thread Id:{0},Count:{1}",Thread.CurrentThread.ManagedThreadId,
            w2.EndWork(r));
            }, null);
            Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);
            Console.ReadLine();
        }

The following is a simple program call process I have drawn to help you understand:

Standard APM model asynchronous programming is too complex for the developer.So asynchronous programming by implementing the IAsyncResult interface is said to be useless (guilt....).

Delegate asynchronous programming (APM standard implementation)

Delegates in C# naturally support asynchronous invocation (APM model), and after any delegate object,'."you will find three methods: BeginInvoke, EndInvoke, and Invoke.BeginInvoke calls delegates asynchronously, EndInvoke waits for delegates to end asynchronously, and Invoke calls delegates synchronously.Therefore, the standard APM instances above can be simplified as follows with delegate.

The NewWorker above was overridden as follows using delegation:

    public class NewWorker2
    {
        Func<int, int, int> action;
        public NewWorker2()
        {
            action = new Func<int, int, int>(Work);
        }
        public IAsyncResult BeginWork(AsyncCallback callback, object state)
        {
            dynamic obj = state;
            return action.BeginInvoke(obj.A, obj.B, callback, this);
        }

        public int EndWork(IAsyncResult asyncResult)
        {
            try
            {
                return action.EndInvoke(asyncResult);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private int Work(int a, int b)
        {
            Thread.Sleep(1000);
            return a + b;
        }
    }

Caller:

        static void Main(string[] args)
        {
            NewWorker2 w2 = new NewWorker2();
            IAsyncResult r = null;
            r = w2.BeginWork((obj) =>
            {
                Console.WriteLine("Thread Id:{0},Count:{1}", Thread.CurrentThread.ManagedThreadId,
                    w2.EndWork(r));
            }, new { A = 10, B = 11 });
            Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);

            Console.ReadLine();
        }

APM asynchronous programming using delegates above is much simpler and easier to understand than implementing the IAsyncResult interface.So here's a suggestion for friends that delegate asynchronous invocation models should be mastered, and your preferences can be seen in the legend of implementing the IAsyncResult interface.

In the delegate above, we also use some knowledge of delegates, anonymous objects, dynamic types, and so on. If you are interested, you can find relevant knowledge from my Blog for your reference.

Last time ad: Friends who like my article, please pay attention to my blog, thank you.

Topics: C# Programming Lambda Attribute