[C#] 走进异步编程的世界 - 在 GUI 中执行异步操作
走进异步编程的世界 - 在 GUI 中执行异步操作
【博主】反骨仔 【原文地址】http://www.cnblogs.com/liqingwen/p/5877042.html
序
这是继《开始接触 async/await 异步编程》、《走进异步编程的世界 - 剖析异步方法》后的第三篇。主要介绍在 WinForm 中如何执行异步操作。
目录
一、在 WinForm 程序中执行异步操作
下面通过窗体示例演示以下操作-点击按钮后:
①将按钮禁用,并将标签内容改成:“Doing”(表示执行中);
②线程挂起3秒(模拟耗时操作);
③启用按钮,将标签内容改为:“Complete”(表示执行完成)。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} private void btnDo_Click(object sender, EventArgs e)
{
btnDo.Enabled = false;
lblText.Text = @"Doing"; Thread.Sleep(); btnDo.Enabled = true;
lblText.Text = @"Complete";
}
}
可是执行结果却是:

图1-1
【发现的问题】
①好像没有变成“Doing”?
②并且拖动窗口的时候卡住不动了?
③3秒后突然变到想拖动到的位置?
④同时文本变成“Complete”?
【分析】GUI 程序在设计中要求所有的显示变化都必须在主 GUI 线程中完成,如点击事件和移动窗体。Windows 程序时通过 消息来实现,消息放入消息泵管理的消息队列中。点击按钮时,按钮的Click消息放入消息队列。消息泵从队列中移除该消息,并开始处理点击事件的代码,即 btnDo_Click 事件的代码。
btnDo_Click 事件会将触发行为的消息放入队列,但在 btnDo_Click 时间处理程序完全退出前(线程挂起 3 秒退出前),消息都无法执行。(3 秒后)接着所有行为都发生了,但速度太快肉眼无法分辨才没有发现标签改成“Doing”。

图1-2 点击事件

图1-3 点击事件具体执行过程
现在我们加入 async/await 特性。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} private async void btnDo_Click(object sender, EventArgs e)
{
btnDo.Enabled = false;
lblText.Text = @"Doing"; await Task.Delay(); btnDo.Enabled = true;
lblText.Text = @"Complete";
}
}

图1-4
现在,就是原先希望看到的效果。
【分析】btnDo_Click 事件处理程序先将前两条消息压入队列,然后将自己从处理器移出,在3秒后(等待空闲任务完成后 Task.Delay )再将自己压入队列。这样可以保持响应,并保证所有的消息可以在线程挂起的时间内被处理。
1.1 Task.Yield
Task.Yield 方法创建一个立刻返回的 awaitable。等待一个Yield可以让异步方法在执行后续部分的同时返回到调用方法。可以将其理解为 离开当前消息队列,回到队列末尾,让 CPU 有时间处理其它任务。
class Program
{
static void Main(string[] args)
{
const int num = ;
var t = DoStuff.Yield1000(num); Loop(num / );
Loop(num / );
Loop(num / ); Console.WriteLine($"Sum: {t.Result}"); Console.Read();
} /// <summary>
/// 循环
/// </summary>
/// <param name="num"></param>
private static void Loop(int num)
{
for (var i = ; i < num; i++) ;
}
} internal static class DoStuff
{
public static async Task<int> Yield1000(int n)
{
var sum = ;
for (int i = ; i < n; i++)
{
sum += i;
if (i % == )
{
await Task.Yield(); //创建异步产生当前上下文的等待任务
}
} return sum;
}
}

图1.1-1
上述代码每执行1000次循环就调用 Task.Yield 方法创建一个等待任务,让处理器有时间处理其它任务。该方法在 GUI 程序中是比较有用的。
二、在 WinForm 中使用异步 Lambda 表达式
将刚才的窗口程序的点击事件稍微改动一下。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent(); //async (sender, e) 异步表达式
btnDo.Click += async (sender, e) =>
{
Do(false, "Doing"); await Task.Delay(); Do(true, "Finished");
};
} private void Do(bool isEnable, string text)
{
btnDo.Enabled = isEnable;
lblText.Text = text;
}
}
还是原来的配方,还是熟悉的味道,还是原来哪个窗口,变的只是内涵。

图2-1
三、一个完整的 WinForm 程序
现在在原来的基础上添加了进度条,以及取消按钮。
public partial class Form1 : Form
{
private CancellationTokenSource _source;
private CancellationToken _token; public Form1()
{
InitializeComponent();
} /// <summary>
/// Do 按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnDo_Click(object sender, EventArgs e)
{
btnDo.Enabled = false; _source = new CancellationTokenSource();
_token = _source.Token; var completedPercent = ; //完成百分比
const int time = ; //循环次数
const int timePercent = / time; //进度条每次增加的进度值 for (var i = ; i < time; i++)
{
if (_token.IsCancellationRequested)
{
break;
} try
{
await Task.Delay(, _token);
completedPercent = (i + ) * timePercent;
}
catch (Exception)
{
completedPercent = i * timePercent;
}
finally
{
progressBar.Value = completedPercent;
}
} var msg = _token.IsCancellationRequested ? $"进度为:{completedPercent}% 已被取消!" : $"已经完成"; MessageBox.Show(msg, @"信息"); progressBar.Value = ;
InitTool();
} /// <summary>
/// 初始化窗体的工具控件
/// </summary>
private void InitTool()
{
progressBar.Value = ;
btnDo.Enabled = true;
btnCancel.Enabled = true;
} /// <summary>
/// 取消事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnCancel_Click(object sender, EventArgs e)
{
if (btnDo.Enabled) return; btnCancel.Enabled = false;
_source.Cancel();
}
}

图3-1
四、另一种异步方式 - BackgroundWorker 类
与 async/await 不同的是,你有时候可能需要一个额外的线程,在后台持续完成某项任务,并不时与主线程通信,这时就需要用到 BackgroundWorker 类。主要用于 GUI 程序。
书中的千言万语不及一个简单的示例。
public partial class Form2 : Form
{
private readonly BackgroundWorker _worker = new BackgroundWorker(); public Form2()
{
InitializeComponent(); //设置 BackgroundWorker 属性
_worker.WorkerReportsProgress = true; //能否报告进度更新
_worker.WorkerSupportsCancellation = true; //是否支持异步取消 //连接 BackgroundWorker 对象的处理程序
_worker.DoWork += _worker_DoWork; //开始执行后台操作时触发,即调用 BackgroundWorker.RunWorkerAsync 时触发
_worker.ProgressChanged += _worker_ProgressChanged; //调用 BackgroundWorker.ReportProgress(System.Int32) 时触发
_worker.RunWorkerCompleted += _worker_RunWorkerCompleted; //当后台操作已完成、被取消或引发异常时触发
} /// <summary>
/// 当后台操作已完成、被取消或引发异常时发生
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(e.Cancelled ? $@"进程已被取消:{progressBar.Value}%" : $@"进程执行完成:{progressBar.Value}%");
progressBar.Value = ;
} /// <summary>
/// 调用 BackgroundWorker.ReportProgress(System.Int32) 时发生
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage; //异步任务的进度百分比
} /// <summary>
/// 开始执行后台操作触发,即调用 BackgroundWorker.RunWorkerAsync 时发生
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void _worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
if (worker == null)
{
return;
} for (var i = ; i < ; i++)
{
//判断程序是否已请求取消后台操作
if (worker.CancellationPending)
{
e.Cancel = true;
break;
} worker.ReportProgress((i + ) * ); //触发 BackgroundWorker.ProgressChanged 事件
Thread.Sleep(); //线程挂起 250 毫秒
}
} private void btnDo_Click(object sender, EventArgs e)
{
//判断 BackgroundWorker 是否正在执行异步操作
if (!_worker.IsBusy)
{
_worker.RunWorkerAsync(); //开始执行后台操作
}
} private void btnCancel_Click(object sender, EventArgs e)
{
_worker.CancelAsync(); //请求取消挂起的后台操作
}
}

图4-1
传送门
入门:《走进异步编程的世界 - 开始接触 async/await 异步编程》
上篇:《走进异步编程的世界 - 剖析异步方法(上)》《走进异步编程的世界 - 剖析异步方法(下)》
【参考】《Illustrated C# 2012》
[C#] 走进异步编程的世界 - 在 GUI 中执行异步操作的更多相关文章
- 走进异步编程的世界 - 在 GUI 中执行异步操作
转载:https://www.cnblogs.com/liqingwen/p/5877042.html 走进异步编程的世界 - 在 GUI 中执行异步操作 [博主]反骨仔 [原文地址]http://w ...
- [C#] 走进异步编程的世界 - 开始接触 async/await
走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...
- [C#] 走进异步编程的世界 - 剖析异步方法(上)
走进异步编程的世界 - 剖析异步方法(上) 序 这是上篇<走进异步编程的世界 - 开始接触 async/await 异步编程>(入门)的第二章内容,主要是与大家共同深入探讨下异步方法. 本 ...
- [C#] 走进异步编程的世界 - 剖析异步方法(下)
走进异步编程的世界 - 剖析异步方法(下) 序 感谢大家的支持,这是昨天发布<走进异步编程的世界 - 剖析异步方法(上)>的补充篇. 目录 异常处理 在调用方法中同步等待任务 在异步方法中 ...
- 走进异步编程的世界 - 开始接触 async/await(转)
序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $"" 来拼接字符串,相当于string.Fo ...
- 走进异步编程的世界 - 开始接触 async/await
[C#] 走进异步编程的世界 - 开始接触 async/await 走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async ...
- [C#] 走进异步编程的世界 - 开始接触 async/await(转)
原文链接:http://www.cnblogs.com/liqingwen/p/5831951.html 走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 ...
- 走进异步编程的世界--async/await项目使用实战
起因:今天要做一个定时器任务:五分钟查询一次数据库发现超时未支付的订单数据将其状态改为已经关闭(数据量大约100条的情况) 开始未使用异步: public void SelfCloseGpPayOrd ...
- 走进windows编程的世界-----入门篇
1 Windows编程基础 1.1Win32应用程序基本类型 1) 控制台程序 不须要完好的windows窗体,能够使用DOS窗体方式显示 2) Win32窗体程序 包括窗体的程序,能够通过窗 ...
随机推荐
- 采用MiniProfiler监控EF与.NET MVC项目(Entity Framework 延伸系列1)
前言 Entity Framework 延伸系列目录 今天来说说EF与MVC项目的性能检测和监控 首先,先介绍一下今天我们使用的工具吧. MiniProfiler~ 这个东西的介绍如下: MVC Mi ...
- 在.Net中实现自己的简易AOP
RealProxy基本代理类 RealProxy类提供代理的基本功能.这个类中有一个GetTransparentProxy方法,此方法返回当前代理实例的透明代理.这是我们AOP实现的主要依赖. 新建一 ...
- Web Api 入门实战 (快速入门+工具使用+不依赖IIS)
平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...
- ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由
原文:Routing to Controller Actions 作者:Ryan Nowak.Rick Anderson 翻译:娄宇(Lyrics) 校对:何镇汐.姚阿勇(Dr.Yao) ASP.NE ...
- 我大中华微软MVP中国区人才库
刘海峰:国内知名微软开源技术网站51Aspx 创始人,十年以上的Asp.net从业经验,微软MSDN特约讲师.Teched讲师.ImagineCup大赛评委.人大出版社研修班特约讲师,曾多次受邀访问美 ...
- 屌丝giser成长记-大学篇
作为一名屌丝giser的我,刚接触gis专业是2007年的大一,好悲催,当时gis这个专业是被调剂的,我压根都不知道gis为何物,那时候gis冷门的一逼,报名这个专业的寥寥无几.记得那时候得知被调剂到 ...
- JQuery阻止事件冒泡
冒泡事件就是点击子节点,会向上触发父节点,祖先节点的点击事件. 我们在平时的开发过程中,肯定会遇到在一个div(这个div可以是元素)包裹一个div的情况,但是呢,在这两个div上都添加了事件,如果点 ...
- 超全面的.NET GDI+图形图像编程教程
本篇主题内容是.NET GDI+图形图像编程系列的教程,不要被这个滚动条吓到,为了查找方便,我没有分开写,上面加了目录了,而且很多都是源码和图片~ (*^_^*) 本人也为了学习深刻,另一方面也是为了 ...
- SqlServer之数据库三大范式
分析: 数据库设计应遵循三大范式分别为: 第一范式:确保表中每列的原子性(不可拆分): 第二范式:确保表中每列与主键相关,而不能只与主键的某部分相关(主要针对联合主键),主键列与非主键列遵循完全函数依 ...
- 用javascript写星际飞机大战游戏
在github里看到了个不错的脚本游戏,决定亲自动手来写,效果如下 下面是代码的思路分享 把整个代码理解消化确实不容易,但是如果你坚持看完相信你一定会有收获 如果没兴趣可以直接点击下面的链接 复制代码 ...