Note 10: introduction to async & await

Keywords: C# Lambda Attribute

Task. Field

Task.Yield is simply a task that has been completed at the time of creation, or a task with execution time of 0, or an empty task. In other words, the iscompleted value of the task is set to 0 at the time of creation.

We know that when the Task of await is completed, the thread will be released, and then a new thread will be applied from the thread pool to continue executing the code after await. What is the significance of the generated empty Task?

In fact, the empty task generated by Task.Yield only uses await as a dowry to achieve the purpose of thread switching, that is, to let the operation after await go back to the thread pool and queue for a new thread to continue.

In this way, if there is a task with low priority but long execution time, it can be divided into multiple small tasks. After each small task is completed, it will be re queued in the thread pool to apply for a new thread to execute

The next small task, so that the task won't always occupy a thread (grant the execution right), so that other tasks with high priority or short execution time can be executed, instead of staring.

    class Program
    {
        static void Main(string[] args)
        {
            #region async & await Introduction 3 Task.Yield 
            const int num = 10000;
            var task = YieldPerTimes(num);

            for (int i = 0; i < 10; i++)
            {
                Task.Factory.StartNew(n => Loop((int)n), num / 10);
            }

            Console.WriteLine($"Sum: {task.Result}");
            Console.Read();
            #endregion
        }

        /// <summary>
        /// loop
        /// </summary>
        /// <param name="num"></param>
        private static void Loop(int num)
        {
            for (var i = 0; i < num; i++) ;
            Console.WriteLine($"Loop->Current thread id is:{Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(10);
        }

        /// <summary>
        /// Execution right of partial assignment
        /// </summary>
        /// <param name="times"></param>
        /// <returns></returns>
        private static async Task<int> YieldPerTimes(int num)
        {
            var sum = 0;
            for (int i = 1; i <= num; i++)
            {
                sum += i;
                if (i % 1000 == 0)
                {
                    Console.WriteLine($"Yield->Current thread id is:{Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(10);
                    await Task.Yield();
                }
            }
            return sum;
        }
    }

The operation results are as follows:

Use asynchronous Lambda expression in WinForm

        public Main()
        {
            InitializeComponent();

            //Asynchronous expression: async (sender, e)
            btnDoIt.Click += async (sender, e) =>
            {
                DoIt(false, "Start moving bricks...");
                await Task.Delay(3000);
                DoIt(true, "Finally, the move is over.");
            };
        }

        private void DoIt(bool isEnable, string text)
        {
            btnDoIt.Enabled = isEnable;
            lblText.Text = text;
        }

The operation results are as follows:

III. scroll bar application

        private CancellationTokenSource source;
        private CancellationToken token;

        public ProcessBar()
        {
            InitializeComponent();
        }

        /// <summary>
        /// initialization
        /// </summary>
        private void InitTool()
        {
            progressBar1.Value = 0;
            btnDoIt.Enabled = true;
            btnCancel.Enabled = true;
        }

        /// <summary>
        /// Start task
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnDoIt_Click(object sender, EventArgs e)
        {
            btnDoIt.Enabled = false;

            source = new CancellationTokenSource();
            token = source.Token;

            var completedPercent = 0;               //% complete
            const int loopTimes = 10;               //Number of cycles
            const int increment = 100 / loopTimes;  //Progress value of each increase of progress bar

            for (var i = 1; i <= loopTimes; i++)
            {
                if (token.IsCancellationRequested)
                {
                    break;
                }

                try
                {
                    await Task.Delay(200, token);
                    completedPercent = i * increment;
                }
                catch (Exception)
                {
                    completedPercent = i * increment;
                }
                finally
                {
                    progressBar1.Value = completedPercent;
                }
            }

            var msg = token.IsCancellationRequested ? $"The task has been cancelled and the execution progress is:{completedPercent}%. " : $"Task execution completed.";
            MessageBox.Show(msg, "Tips", MessageBoxButtons.OK, MessageBoxIcon.Information);

            progressBar1.Value = 0;
            InitTool();
        }

        /// <summary>
        /// Cancel task
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCancel_Click(object sender, EventArgs e)
        {
            if (btnDoIt.Enabled) return;

            btnCancel.Enabled = false;
            source.Cancel();
        }
    }

The operation results are as follows:

IV. BackgroundWorker

Different from async & await, sometimes an extra thread may be needed. It continuously completes a task in the background and communicates with the main thread when it does not. In this case, the BackgroundWorker class is needed.

(mainly used for GUI program)

        private readonly BackgroundWorker bgWorker = new 
        BackgroundWorker();

        public ProcessBar()
        {
            InitializeComponent();

            //Set up BackgroundWorker attribute
            bgWorker.WorkerReportsProgress = true;      //Can progress updates be reported
            bgWorker.WorkerSupportsCancellation = true; //Whether asynchronous cancellation is supported

            //connect BackgroundWorker Object's handler
            bgWorker.DoWork += bgWorker_DoWork;
            bgWorker.ProgressChanged += bgWorker_ProgressChanged;
            bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
        }

        /// <summary>
        /// Start execution of background operation trigger, i.e. call BackgroundWorker.RunWorkerAsync Occurs.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (!(sender is BackgroundWorker worker))
            {
                return;
            }

            for (var i = 1; i <= 10; i++)
            {
                //Determine whether the program has requested to cancel the background operation
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }

                worker.ReportProgress(i * 10);  //trigger BackgroundWorker.ProgressChanged Event
                Thread.Sleep(200);              //Thread suspend 200 ms
            }
        }

        /// <summary>
        /// call BackgroundWorker.ReportProgress(System.Int32)Occur when
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;  //Progress percentage of asynchronous tasks
        }

        /// <summary>
        /// Occurs when a background operation has completed or has been cancelled or an exception has been thrown
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show(e.Cancelled ? $@"The task has been cancelled, and the execution progress is:{progressBar1.Value}%" : $@"The task is completed, and the executed progress is:{progressBar1.Value}%");
            progressBar1.Value = 0;
        }

        /// <summary>
        /// Start task
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDoIt_Click(object sender, EventArgs e)
        {
            //judge BackgroundWorker Is an asynchronous operation in progress
            if (!bgWorker.IsBusy)
            {
                bgWorker.RunWorkerAsync();  //Start background operation
            }
        }

        /// <summary>
        /// Cancel task
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCancel_Click(object sender, EventArgs e)
        {
            bgWorker.CancelAsync(); //Request to cancel a pending background operation
        }

The operation results are as follows:

Reference from:

    https://www.cnblogs.com/dudu/archive/2018/10/24/task-yield.html

    https://www.cnblogs.com/liqingwen/p/5877042.html

Postscript:

For more detailed BackgroundWorker knowledge, see this blog:

    https://www.cnblogs.com/sparkdev/p/5906272.html

Posted by elgordo1960 on Tue, 17 Dec 2019 22:43:49 -0800