一、Task.Yield Task.Yield簡單來說就是創建時就已經完成的Task,或者說執行時間為0的Task,或者說是空任務,也就是在創建時就將Task的IsCompeted值設置為0。 我們知道await的Task完成時會釋放線程,然後從線程池中申請新的線程繼續執行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