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; } }
运行结果如下:
二、在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; }
运行结果如下:
三、滚动条应用
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(); } }
运行结果如下:
四、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(); //请求取消挂起的后台操作 }
运行结果如下:
参考自:
https://www.cnblogs.com/dudu/archive/2018/10/24/task-yield.html
https://www.cnblogs.com/liqingwen/p/5877042.html
后记:
关于更详细的BackgroundWorker知识,可查看此篇博客: