C#線程學習筆記十:async & await入門三


    一、Task.Yield

    Task.Yield簡單來說就是創建時就已經完成的Task,或者說執行時間為0的Task,或者說是空任務,也就是在創建時就將Task的IsCompeted值設置為0。

    我們知道await的Task完成時會釋放線程,然后從線程池中申請新的線程繼續執行await之后的代碼,那產生的空任務又意義何在呢?

    事實上,Task.Yield產生的空任務僅僅是借await做嫁衣來達到線程切換的目的,即讓await之后的操作重新去線程池排隊申請新線程來繼續執行。

    這樣一來,假如有一個優先級低但執行時間長的任務,可以將它拆分成多個小任務,每個小任務執行完成后就重新去線程池中排隊申請新線程來執行

下一個小任務,這樣任務就不會一直霸占着某個線程了(出讓執行權),讓別的優先急高或執行時間短的任務可以去執行,而不是干瞪眼着急。

    class Program
    {
        static void Main(string[] args)
        {
            #region async & await入門三之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>
        /// 循環
        /// </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>
        /// 分批出讓執行權
        /// </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;
        }
    }
View Code

    運行結果如下:

    二、在WinForm中使用異步Lambda表達式

        public Main()
        {
            InitializeComponent();

            //異步表達式:async (sender, e)
            btnDoIt.Click += async (sender, e) =>
            {
                DoIt(false, "開始搬磚啦...");
                await Task.Delay(3000);
                DoIt(true, "終於搬完了。");
            };
        }

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

    運行結果如下:

    三、滾動條應用

        private CancellationTokenSource source;
        private CancellationToken token;

        public ProcessBar()
        {
            InitializeComponent();
        }

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

        /// <summary>
        /// 開始任務
        /// </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;               //完成百分比
            const int loopTimes = 10;               //循環次數
            const int increment = 100 / loopTimes;  //進度條每次增加的進度值

            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 ? $"任務被取消,已執行進度為:{completedPercent}%。" : $"任務執行完成。";
            MessageBox.Show(msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);

            progressBar1.Value = 0;
            InitTool();
        }

        /// <summary>
        /// 取消任務
        /// </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();
        }
    }
View Code

    運行結果如下:

    四、BackgroundWorker

    與async & await不同的是,有時候可能需要一個額外的線程,它在后台持續完成某個任務並不時與主線程通信,這時就需要用到BackgroundWorker類。

(主要用於GUI程序)

        private readonly BackgroundWorker bgWorker = new 
        BackgroundWorker();

        public ProcessBar()
        {
            InitializeComponent();

            //設置BackgroundWorker屬性
            bgWorker.WorkerReportsProgress = true;      //能否報告進度更新
            bgWorker.WorkerSupportsCancellation = true; //是否支持異步取消

            //連接BackgroundWorker對象的處理程序
            bgWorker.DoWork += bgWorker_DoWork;
            bgWorker.ProgressChanged += bgWorker_ProgressChanged;
            bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
        }

        /// <summary>
        /// 開始執行后台操作觸發,即調用BackgroundWorker.RunWorkerAsync時發生。
        /// </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++)
            {
                //判斷程序是否已請求取消后台操作
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }

                worker.ReportProgress(i * 10);  //觸發BackgroundWorker.ProgressChanged事件
                Thread.Sleep(200);              //線程掛起200毫秒
            }
        }

        /// <summary>
        /// 調用BackgroundWorker.ReportProgress(System.Int32)時發生
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;  //異步任務的進度百分比
        }

        /// <summary>
        /// 當后台操作已完成或被取消或引發異常時發生
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show(e.Cancelled ? $@"任務已被取消,已執行進度為:{progressBar1.Value}%" : $@"任務執行完成,已執行進度為:{progressBar1.Value}%");
            progressBar1.Value = 0;
        }

        /// <summary>
        /// 開始任務
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDoIt_Click(object sender, EventArgs e)
        {
            //判斷BackgroundWorker是否正在執行異步操作
            if (!bgWorker.IsBusy)
            {
                bgWorker.RunWorkerAsync();  //開始執行后台操作
            }
        }

        /// <summary>
        /// 取消任務
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCancel_Click(object sender, EventArgs e)
        {
            bgWorker.CancelAsync(); //請求取消掛起的后台操作
        }
View Code

    運行結果如下:

    參考自:

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

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

    后記:

    關於更詳細的BackgroundWorker知識,可查看此篇博客:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM