[C#] Parallel Programming in Practice: Asynchronous Streaming

        Originally this chapter was supposed to talk about IIS and Kestrel in ASP.NET Core, but I saw that this one is for the server . And I am just a Unity client program, and my understanding of the server is close to zero.

        In view of my lack of server knowledge and needs, I won't go into (most of) the original book here. There is a part of this chapter that the client can also learn, which is asynchronous streaming. So this chapter will be changed to just learn asynchronous streams.

        Learn Engineering with this Tutorial: Magician Dix/HandsOnParallelProgramming · GitCode


1. Introduction to asynchronous streams

        .Net Core 3.0 also introduces Asynchronous Stream support.

Asynchronous return types | Microsoft Learn Learn the return types an asynchronous method can have in C#, along with code examples for each type. icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/csharp/asynchronous-programming/async-return-types

        This new feature enables developers to wait for a foreach loop on an IAsyncEnumerable<T> to consume elements from the stream, and use yield to return the stream to produce the elements. Asynchronous streams not only support large amounts of data, but also enable the server to respond at the same time by efficiently utilizing threads.

2. Introduction to grammar

        According to this chapter, we use two interfaces here: IAsyncEnumerable<T> and IAsyncEnumerator<T>. The implementation of the interface is easier to say, just look at the code:

    public class MyAsyncEnumerable : IAsyncEnumerable<int>
    {

        private MyAsyncEnumerator mAsyncEnumerator;

        public IAsyncEnumerator<int> GetAsyncEnumerator(CancellationToken cancellationToken = default)
        {
            if (mAsyncEnumerator == null)
                mAsyncEnumerator = new MyAsyncEnumerator();
            return mAsyncEnumerator;
        }
    }
    
    public class MyAsyncEnumerator : IAsyncEnumerator<int>
    {
        private int m_Value;

        public async ValueTask<bool> MoveNextAsync()
        {
            m_Value++;
            await Task.Delay(1000);
            if (m_Value < 10)
                return true;
            else
                return false;
        }

        public int Current => m_Value;

        public ValueTask DisposeAsync()
        {
            m_Value = 0;
            return new ValueTask(Task.CompletedTask);
        }
    }

        Here we see that every time MoveNext is executed, it will wait for 1s (here is simulating a very time-consuming function), and then continue to execute until it returns false. This is of course the same as the ordinary iterator mode.

3. Test cases

        Let's test it by calling and running the following function directly on the main thread:

        public static async void TestRunWithAsyncStream()
        {
            Debug.Log("TestRunWithAsyncStream Start !");
            MyAsyncEnumerable myEnumerable = new MyAsyncEnumerable();
            await foreach (var item in myEnumerable)
            {
                Debug.Log($"{Task.CurrentId} => {item}");                
            }
            Debug.Log("TestRunWithAsyncStream End !");
        }

        The running results are as follows:

         It can be seen that the main thread is not blocked during the running process, but MoveNext is executed asynchronously and the value is returned to the current loop body. Since the method body is executed on the main thread, it can also be written like this:

        public static async void TestRunWithAsyncStream()
        {
            Debug.Log("TestRunWithAsyncStream Start !");
            MyAsyncEnumerable myEnumerable = new MyAsyncEnumerable();
            await foreach (var item in myEnumerable)
            {
                Debug.Log($"{Task.CurrentId} => {item}");
                //创建一个Obj
                var go= GameObject.CreatePrimitive(PrimitiveType.Cube);
                go.name = $"OBJ_{item}";
            }
            Debug.Log("TestRunWithAsyncStream End !");
        }

        Judging from the running results:

         The GameObject is created asynchronously. It is very convenient to see that asynchronous and main thread are combined together, and there is no difference in use.


4. Summary

        Asynchronous streams are simple to use, but slightly more complicated to write.

        When the amount of data is small, we usually complete all asynchronous operations (such as reading files), and then perform some Unity operations on the main thread. Of course, under normal circumstances, there is no problem. Generally speaking, the data is not large, so there is no problem. But if it is so-called massive data, reading the file takes a long time, and the main thread waits for too long and does not respond, which is not very friendly. In this case, you can use asynchronous stream processing: execute an asynchronous program processing, and immediately execute the main thread processing.

        Moreover, using asynchronous streams can guarantee the order of execution (correspondingly, the degree of parallelism is only 1), which is more suitable for heavy logic of certain process properties.

        Learn Engineering with this Tutorial: Magician Dix/HandsOnParallelProgramming · GitCode

Guess you like

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