.NET中的async和await关键字使用及Task异步调用实例
作者:junjie 发布时间:2021-09-06 03:37:41
其实早在.NET 4.5的时候M$就在.NET中引入了async和await关键字(VB为Async和Await)来简化异步调用的编程模式。我也早就体验过了,现在写一篇日志来记录一下顺便凑日志数量(以后面试之前可以用这个“复习”一下)。
(一)传统的异步调用
在比较“古老”的C#程序中经常可以看到IAsyncResult、BeginInvoke之类的异步调用“踪迹”。先来简单的复习一下吧。
假如我们有一个方法生成字符串,而生成这个字符串需要10秒中的时间:
public class WasteTimeObject
{
public string GetSlowString(int begin, int length)
{
StringBuilder sb = new StringBuilder();
for (int i = begin; i < begin + length; i++)
{
sb.Append(WasteTime(i) + " ");
}
return sb.ToString();
}
private string WasteTime(int current)
{
System.Threading.Thread.Sleep(1000);
return current.ToString();
}
}
我们再做一个窗口,用来请求这个方法并把字符串显示到文本框中。使用同步调用肯定会把UI线程阻塞掉,要想不把UI阻塞掉就要另起一个线程了。基本的步骤如下:
创建一个异步调用的委托:
public delegate string GetSlowStringDelegate(int begin, int length);
然后呢,再异步调用这个委托:
private void button1_Click(object sender, EventArgs e)
{
WasteTimeObject ad = new WasteTimeObject();
GetSlowStringDelegate d = ad.GetSlowString;
textBox1.Text = "Requesting string, please wait...";
IAsyncResult ar = d.BeginInvoke(1, 10, TaskComplete, d);
}
这里的BeginInvoke会在原来的基础上再附加两个参数:表示执行完毕后的回调方法AsyncCallBack,最后一个参数可以是任何对象,以便从回调方法中访问它。不过一般情况都是传递的委托实例,以便获取调用的结果。
当然我们也可以不用回调方法,这样就只好不断地循环查询是否执行完成了。
然后我们就要编写AsyncCallBack这个回调方法了,它接受一个IAsyncResult类型的对象表示异步调用的结果:
private void TaskComplete(IAsyncResult ar)
{
if (ar == null) return;
GetSlowStringDelegate d = ar.AsyncState as GetSlowStringDelegate;
if (d == null) throw new Exception("Invalue object type");
string result = d.EndInvoke(ar);
this.Invoke(new Action(() => UpdateTextResult(result)));
}
调用委托实例的EndInvoke方法并传入IAsyncResult类型的对象用以获取GetSlowString的返回结果。
回调方法是委托线程调用的,因此它不能直接访问UI,所以我们使用窗体的Invoke方法在主线程中显示结果。如果委托方法抛出异常,将会在EndInvoke时抛出。
(二)使用Task类型
可以看到使用传统的办法编写异步调用很麻烦,特别是如果这种调用很多,那么我们的程序就会变成很复杂,逻辑很乱。
.NET 4.5提供的新的异步变成模式就很好地解决了这个问题(其实本质上应该是.NET自动实现了很多操作),使编写异步代码和同步调用一样逻辑清晰。
首先来看看微软的例子:
private async Task SumPageSizesAsync()
{
// To use the HttpClient type in desktop apps, you must include a using directive and add a
// reference for the System.Net.Http namespace.
HttpClient client = new HttpClient();
// Equivalently, now that you see how it works, you can write the same thing in a single line.
byte[] urlContents = await client.GetByteArrayAsync(url);
// . . .
}
可以看出,使用await关键字后,.NET会自动把返回结果包装在一个Task类型的对象中。对于这个示例,方法是没有返回结果的。而对有返回结果的方法,就要使用Task<T>了:
public async Task<string> WaitAsynchronouslyAsync()
{
await Task.Delay(10000);
return "Finished";
}
总而言之,使用await表达式时,控制会返回到调用此方法的线程中;在await等待的方法执行完毕后,控制会自动返回到下面的语句中。发生异常时,异常会在await表达式中抛出。
对于我们这个例子,我们编写的代码如下:
private async void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "Requesting string, please wait...";
WasteTimeObject ad = new WasteTimeObject();
string result = await Task.Run(() => ad.GetSlowString(1, 10));
//Update UI to display the result
textBox1.Text = result;
}
我们使用Task类新建一个工作线程并执行。当然我们也可以像M$给的例子那样改造一下GetSlowString,这样就不需要加上Task.Run了。(基本上,这种方法都会以Async后缀结尾。)
如何?原来的:创建异步委托→回调一气呵成。另外还有一点,await下面的语句是由主线程调用的,不是由新的线程调用,所以我们可以直接访问UI。
(三)取消执行和显示进度
最后一个要记录的,就是如何给异步调用添加进度条,并能让用户取消操作。界面就是下面这样:
使用最终完成的代码来说明吧。首先改造GetSlowString方法,使之支持取消和汇报进度:
public string GetSlowString(int begin, int length, IProgress<int> progress, CancellationToken cancel)
{
StringBuilder sb = new StringBuilder();
for (int i = begin; i < begin + length; i++)
{
sb.Append(WasteTime(i) + " ");
cancel.ThrowIfCancellationRequested();
if (progress != null)
progress.Report((int)((double)(i - begin + 1) * 100 / length));
}
return sb.ToString();
}
IProgress<T>类型的对象有一个Report方法,执行这个方法实际上会调用自定义的更新进度的方法,这个方法(使用委托或匿名方法皆可)是在生成Progress<T>对象的时候指定的:
IProgress<int> progress = new Progress<int>((progressValue) => { progressBar1.Value = progressValue; });
神奇的是,这个方法是由主线程调用的,如果不是这样,它就不能更新我们界面上的控件。所以说微软提供的新机制帮我们简化了很多工作。
CancellationToken用于指定该方法“绑定”的取消上下文,如果这个对象执行过Cancel方法(用户点击了Cancel按钮),那么访问ThrowIfCancellationRequested时就会抛出OperationCanceledException类型的异常。这种机制的灵活性在于中止执行的位置是可以自行确定的,不会出现取消时自己都不知道执行到哪行代码的情况。
总而言之,单击request按钮的代码我们修改如下:
private async void button1_Click(object sender, EventArgs e)
{
cancelSource = new CancellationTokenSource();
IProgress<int> progress = new Progress<int>((progressValue) => { progressBar1.Value = progressValue; });
textBox1.Text = "Requesting string, please wait...";
button1.Enabled = false; button2.Enabled = true;
WasteTimeObject ad = new WasteTimeObject();
try
{
string result = await Task.Run(() => ad.GetSlowString(1, 10, progress, cancelSource.Token),
cancelSource.Token);
//Update UI to display the result
textBox1.Text = result;
button2.Enabled = false; //Disable cancel button
}
catch (OperationCanceledException)
{
textBox1.Text = "You canceled the operation.";
}
}
取消按钮的代码就很简单了:
private void button2_Click(object sender, EventArgs e)
{
if (cancelSource != null) cancelSource.Cancel();
button2.Enabled = false;
}
至此,Task机制的初步体验就到此完成。以后有机会在研究下更高阶的内容吧。


猜你喜欢
- 这篇文章主要介绍了SpringBoot使用Log4j过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需
- 引入线程是为了减少程序在并发执行时所付出的时空开销。属性:轻型实体。它不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。独立调度和
- 在定义API的时候,对于一些返回集合对象的方法,很多人喜欢将返回类型定义成IEnumerable<T>,这本没有什么问题。这里要
- 前言在产品发布前夕,经常因为编写各类设计文档感到心碎,倒不是难,而是比较繁琐,举例来说,像编写数据库文档这种操作来说,对于新手,甚至很多有一
- 本文实例为大家分享了toolabar结合drawlayout使用方法,供大家参考,具体内容如下package alice.bw.com.da
- 分页application.ymlspring: datasource: url: jdbc:mysql://127.0.0.1/jpa?u
- 在Android开发中在所难免的会出现程序crash,俗称崩溃。用户的随意性访问出现测试时未知的Bug导致我们的程序crash,此时我们是无
- 最新Android版本、API级别与代号对应关系代号版本号API/NDK级别发布时间-O8.0API level 262017-3-21牛轧
- 前言我们平时在开发的时候,发起网络请求前,会需要显示一个Loading,一般的做法都是在xml布局上添加好Loading,然后在Activi
- 找了很久查询objectid的方法都是错的,用mongovue能查询出来,但就是用java不知道怎么查询1.mongovue里的查询方式:{
- 为了重用Fragment UI 组件,在设计中你应该通过定义每一个fragemnt自己的layout和行为,让fragment的自包含和模块
- 今天在研究dubbo时,发现一个新的知识点,可以使用javassist包进行动态编程,hibernate也使用该包进行编程。晚上百度了很多资
- Parallel类是对线程的抽象,提供数据与任务的并行性。类定义了静态方法For和ForEach,使用多个任务来完成多个作业。Paralle
- 配置文件<!-- 文件上传 --> <bean id="multipartResolver" clas
- 生成指定范围内的随机数这个是最常用的技术之一,程序员希望通过随机数的方式来处理众多的业务逻辑,测试过程中也希望通过随机数的方式生成包含大量数
- 在C#中,@符号不仅可以加在字符串常量之前,使字符串不作转义之用,还可以加在变量名之前,使变量名与关键字不冲突,这种用法称为“逐字标识符”。
- 以下教程是小编在参与开发公司的一个crm系统,整理些相关资料,在该系统中有很多消息推送功能,在其中用到了websocket技术。下面小编整理
- 本文实例讲述了C#基于DBContext(EF)实现通用增删改查的REST方法,分享给大家供大家参考。具体如下:我们用ADO.NET Ent
- 需求:1、listView可以侧滑item,展示删除按钮,点击删除按钮,删除当前的item2、在删除按钮展示时,点击隐藏删除按钮,不响应it
- Filter过滤器和Listener * 详解Filter过滤器Filter的简介 对资源的访问进行过滤,相当于小区的保安,进去