一、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知识,可查看此篇博客: