[C#] Parallel programming practice: using PLINQ (2)

PLINQ is a parallel implementation of          Language Integrate Query (  LINQ  )  ( P stands for parallel). This chapter will go on to describe various aspects of its programming and some advantages and disadvantages associated with it.

        This article focuses on the coalescing options in PLINQ and throwing and handling exceptions.

        This tutorial corresponds to Learning Engineering: Magician Dix / HandsOnParallelProgramming · GitCode      


4. Merge options in PLINQ

        As mentioned earlier, when creating a parallel query, the source collection is partitioned so that multiple tasks can work on each part concurrently. After the query is complete, the results need to be combined so that they can be given to the thread that uses him.

4.1. Use the NotBuffered merge option

        When using the NoBuffered coalescing option, the results of concurrent tasks are not buffered. Once any task is done, they return the result to the consuming thread. The code demonstration is as follows:

        private void RunWithNotBuffered()
        {
            var task = Task.Run(() =>
                 {
                     Debug.Log("RunWithNotBuffered Start !");
                     var L = ParallelEnumerable.Range(0, 10);
                     var notBufferedQuery = L.WithMergeOptions(ParallelMergeOptions.NotBuffered)
                         .Select(async x =>
                         {
                             await Task.Delay(x * 1000);
                             return x;
                         });

                     notBufferedQuery.ForEach(t=>
                     {
                         Debug.Log($"{t.Result} In  notBufferedQuery !");
                     });
                     Debug.Log("RunWithNotBuffered End !");
                 });
        }

        The printed results are as follows:

         First of all, the execution is out of order; then, although there is a wait for each selection, it will be executed in ForEach every time a Select statement is executed.

4.2. Use the AutoBuffered merge option

        When using the AutoBuffered coalescing option, the results of concurrent tasks are buffered and the buffer is made available periodically to threads using it. Depending on the size of the collection, multiple buffers may be returned. When this option is set, threads consuming results will wait longer for the first result. This is also the default option.

        private void RunWithAutoBuffered()
        {
            var task = Task.Run(() =>
            {
                Debug.Log("RunWithAutoBuffered Start !");
                var L = ParallelEnumerable.Range(0, 10);
                var autoBufferedQuery = L.WithMergeOptions(ParallelMergeOptions.AutoBuffered)
                    .Select(async x =>
                    {
                        await Task.Delay(x * 1000);
                        return x;
                    });

                autoBufferedQuery.ForEach(t =>
                {
                    Debug.Log($"{t.Result} In  autoBufferedQuery !");
                });
                Debug.Log("RunWithAutoBuffered End !");
            });
        }

        The result of printing this time is more interesting:

         It can be seen that the first result came out after waiting for a long time (8s), but the subsequent results came out soon.

4.3. Use the FullyBuffered merge option

        When using the FullyBuffered coalescing option, the results of concurrent tasks are fully buffered before entering the thread that uses it. Although it will take longer to get the first result, it can improve overall performance.

        The test code is as follows:

        private void RunWithFullyBuffered()
        {
            var task = Task.Run(() =>
            {
                Debug.Log("RunWithFullyBuffered Start !");
                var L = ParallelEnumerable.Range(0, 10);
                var fullyBufferedQuery = L.WithMergeOptions(ParallelMergeOptions.FullyBuffered)
                    .Select(async x =>
                    {
                        await Task.Delay(x * 1000);
                        return x;
                    });

                fullyBufferedQuery.ForEach(t =>
                {
                    Debug.Log($"{t.Result} In  fullyBufferedQuery !");
                });
                Debug.Log("RunWithFullyBuffered End !");
            });
        }

        The printed results are as follows:

         It can be seen that the results are printed in sequence, because the code we set while waiting is (x*1000), that is, the Select items all start at the same time, and then end in sequence.

        Not all query operators support all merge modes. Operators and their merge mode restrictions can refer to the following websites:

Merge Options in PLINQ | Microsoft Learn Learn more about: Merge Options in PLINQ icon-default.png?t=N658https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/merge-options-in-plinq#query-operators- that-support-merge-options         In addition to the above operators, ForAll is always NotBuffered and OrderBy is always FullyBuffered. If any custom merge options are specified on these operators, they are ignored.

5. Use PLINQ to throw and handle exceptions

        Simply use the TryCatch statement to catch exceptions in PLINQ:

        private void RunException()
        {
            var range = ParallelEnumerable.Range(1, 10);
            var query = range.Select(x => x / (x % 2 - 1));
            try
            {
                query.ForAll(x => Debug.Log(x));
            }
            catch (AggregateException ex)
            {
                Debug.LogError(ex.Message);
                var exs = ex.InnerExceptions;
                foreach (var innerEx in exs)
                {
                    Debug.LogError(innerEx.Message);
                }
            }
        }

        The above code, as long as it is an odd number, there will be an error of dividing by 0, and it will be printed directly as follows:

         It can be seen that multiple threads are running, and multiple division by 0 errors have occurred. But I found that the error exception was thrown this time. If the InnerException is printed, the collection may be incomplete and the stack information cannot be displayed. But this is actually acceptable. After all, as long as one error is known, it is the same for multi-threading, and it does not matter how many times the same error occurs.


(to be continued)

 This tutorial corresponds to Learning Engineering: Magician Dix / HandsOnParallelProgramming · GitCode        

Guess you like

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