[C#] Parallel Programming in Practice: Detailed Explanation of Asynchronous Programming

        In previous chapters, we have seen how parallel programming works by creating small tasks called units of work that can be executed simultaneously by one or more threads.

        This chapter will begin with an introduction to the differences between synchronous and asynchronous code, then discuss when asynchronous code is appropriate and when it should be avoided. Finally we'll discuss new features in parallel programming to help address the complexities of asynchronous code.

        This tutorial corresponds to the learning project: Magician Dix / HandsOnParallelProgramming · GitCode

        This chapter is equivalent to a staged summary, there is not much new knowledge, and it is generally relatively simple.


1. Type of program execution

        The book talks about the execution order of synchronous operations and asynchronous operations. I think it is for beginners and does not require additional explanation. Synchronous execution means that the code can only be executed line by line, from top to bottom; if it is asynchronous, multiple code blocks will be executed at the same time. I believe everyone can understand this basic knowledge without going into details. It is not difficult to understand synchronization and asynchronousness.

        Skip here and go to the next part.

2. Situations suitable for using asynchronous programming

        When is it appropriate to use asynchronous programming? Microsoft has a document:

Synchronous and asynchronous operations - WCF | Microsoft Learn Learn about implementing and calling asynchronous service operations. WCF services and clients can use asynchronous operations at both levels of the application. icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/framework/wcf/synchronous-and-asynchronous-operations

        The following scenarios are suggested:

  • If the operation is called from a middle-tier application .

  • If you call the operation in an ASP.NET page, you can use an asynchronous page.

  • If the operation is called from any single-threaded application such as Windows Forms or Windows Presentation Foundation (WPF). When using the asynchronous event-based calling model, the resulting event is raised on the UI thread, adding responsiveness to the application without requiring you to handle multiple threads yourself.

        After a quick look, it has nothing to do with our Unity development. And for Unity development, there have always been clear scenarios for when to use asynchronous multi-threading: network, IO, processing of large amounts of data, etc. In short, for the sake of performance, naturally when a task is placed on the main thread and causes lag, it is necessary to consider whether to use asynchronous multi-threading.

3. Write asynchronous code

        There are the following ways to implement asynchronously:

  • Use the Delegate.BeginInvoke method;

  • Use the Task class;

  • Use the IAsyncResult interface;

  • Use async and await keywords.

3.1. Use the Delegate.BeginInvoke method

        This is a method for delegation. Let’s look at a piece of code directly:

        private void RunWithBegionInvoke()
        {
            Action logAction = AsyncLogAction;
            logAction.BeginInvoke(null, null);
            Debug.Log("RunWithBegionInvoke !!!");
        }
        
        public static async void AsyncLogAction()
        {
            await Task.Delay(2500);
            Debug.Log("AsyncLogAction Finish !!!");
        }

        We execute it directly and the results are as follows:

         It can be seen that BeginInvoke does not block the main thread, but achieves an asynchronous effect. At the same time, this function also supports callbacks and parameters, which can be said to be very easy to use. This bottom layer is actually implemented using IAsyncResult. Of course, automatic implementation will have certain additional overhead.

3.2. Use Task class

        This has been used too much before, so I won’t go into details. You can refer to Chapter 2. Skip.

3.3. Use IAsyncResult interface

IAsyncResult Interface (System) | Microsoft Learn Represents the status of an asynchronous operation. icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.iasyncresult?view=netstandard-2.1         This kind of interface syntax is easier to understand if you directly code it. Here we can define our own IAsyncResult casually, and of course I can write down the parameters inside casually:

    /// <summary>
    /// IAsyncResult 的示例代码;
    /// </summary>
    public class MyAsyncResult : IAsyncResult
    {
        public MyAsyncResult(MyAsyncState state)
        {
            m_AutoResetEvent = new AutoResetEvent(false);
            IsCompleted = false;
            m_AsyncState = state;
            CompletedSynchronously = false;
        }

        private MyAsyncState m_AsyncState;
        public object AsyncState => m_AsyncState;

        private AutoResetEvent m_AutoResetEvent;
        public WaitHandle AsyncWaitHandle => m_AutoResetEvent;

        public bool CompletedSynchronously { get; private set; }
        public bool IsCompleted { get; private set; }

    }
    
    /// <summary>
    ///  自定义的数据结构
    /// </summary>
    public class MyAsyncState
    {

        public Vector3 Positon;
        public Quaternion Rotation;

    }

        Then we try to call him:

        private void RunWtihMyAsyncResult()
        {
            MyAsyncState state = new MyAsyncState();
            state.Positon = new Vector3(50, 811, 55);
            state.Rotation = Quaternion.Euler(7, 8, 9);
            MyAsyncResult result = new MyAsyncResult(state);

            Action logAction = AsyncLogAction;
            // OnActionCallBack 的回调仍然在子线程
            logAction.BeginInvoke(OnActionCallBack, result);

            Debug.Log($"RunWtihMyAsyncResult : {Task.CurrentId}");
        }
        
        public static void OnActionCallBack(IAsyncResult result)
        {
            Debug.Log($"OnActionCallBack : {result.GetType()} | {Task.CurrentId} !");

            MyAsyncResult myResult = result.AsyncState as MyAsyncResult;
            MyAsyncState myState = myResult.AsyncState as MyAsyncState;

            Debug.Log($"Positon : {myState.Positon}");
            Debug.Log($"EulerAngles : {myState.Rotation.eulerAngles}");
            Debug.Log($"IsCompleted : {result.IsCompleted}");
            Debug.Log($"CompletedSynchronously : {result.CompletedSynchronously}");
            Debug.Log($"OnActionCallBack : End  !");
        }

        As you can see, the callback MyAsyncResult here is the AsyncState in the returned IAsyncResult, which is like a nesting doll. The interface implementation returned by the system is the System.Runtime.Remoting.Messaging.AsyncResult class, which should be automatically encapsulated.

        Note: The IAsyncResult interface called by BeginInvoke is still in the child thread!

        Of course, the above writing method found that the IAsyncResult we wrote ourselves is almost useless... However, C# provides a general and simple writing method:

        private void RunWtihAsyncCallBack()
        {
            MyAsyncState state = new MyAsyncState();
            state.Positon = new Vector3(888, 831, 255);
            state.Rotation = Quaternion.Euler(76, 38,329);
            Action logAction = TestFunction.AsyncLogAction;
            logAction.BeginInvoke(TestFunction.OnActionCallBackSimple, state);
        }

        What is returned here is also System.Runtime.Remoting.Messaging.AsyncResult. What is different from what I expected is that the callback is executed before the delegate, so I don’t know what the use of this interface is for the time being.

4. Situations when it is not appropriate to use asynchronous programming

        Here the author provides 4 scenarios:

  • In a single database without connection pooling

  • Pay more attention to the ease of reading and maintaining the code

  • Simple operation and short running time

  • Application uses a lot of shared resources

        In fact, for our Untiy development, these are not problems. Unity originally recommends doing everything on the main thread, and multi-threading is an advanced usage. Programmers who write multithreading generally know when to use multithreading. Since Unity disables many interfaces from being called in sub-threads, in fact most of the code related to Unity can only be used in the main thread. Most of the sub-threads used are IO or pure data processing.


5. Sections of this chapter

        This chapter is actually a summary. It introduces the concepts of asynchronous and synchronized, and also discusses various implementations of asynchronous. But I personally feel that there is not much practical information, and the useful things are not as good as those mentioned in the previous chapters, which is a bit watery.

        This tutorial corresponds to the learning project: Magician Dix / HandsOnParallelProgramming · GitCode

Guess you like

Origin blog.csdn.net/cyf649669121/article/details/131829094