.NET中的async和await关键字使用及Task异步调用实例

其实早在.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机制的初步体验就到此完成。以后有机会在研究下更高阶的内容吧。

(0)

相关推荐

  • 使用Grunt.js管理你项目的应用说明

    Grunt.js是什么?Grunt.js是一个Javascript Task Runner(Javascript任务运行器),其基于NodeJS,可用于自动化构建.测试.生成文档的项目管理工具. Grunt.js并不是仅仅是构建工具,实际上他只是任务运行器,管理每个子任务的自动化运行,我们还能使用他做更多东西. 为什么使用Grunt.js?简单的说:为了自动化.对于前端项目,例如: •为了明确模块分工,我们可能会将Javascript代码拆成很小很小的一个个js文件,但是我们知道,在最终页面上,

  • asynctask的用法详解

    在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行.在单线程模型中始终要记住两条法则: 1. 不要阻塞UI线程 2. 确保只在UI线程中访问Android UI工具包 当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理.所以主线程通常又被叫做UI线程.

  • 使用GruntJS构建Web程序之安装篇

    它有以下作用 合并JS文件    压缩JS文件    单元测试(基于QUnit)    一句话:完全自动化(automation) 以下是它的安装过程. 一.安装node 参考nodejs入门 (最新的node会自动安装npm) 二.安装grunt命令行工具grunt-cli 使用-g全局安装,这样可以在任何一个目录里使用了.命令: npm install -g grunt-cli 需要注意的是在linux或mac下有时会报没有权限的错误,这时须在前面加一个sudo, 安装后,可以查看改工具的版

  • 使用GruntJS链接与压缩多个JavaScript文件过程详解

    自己写了个简单的HTML5 Canvas的图表库,可以支持饼图,折线图,散点图,盒子图 柱状图,同时支持鼠标提示,绘制过程动画效果等.最终我想把这些多个JS文件变成 一个JS文件发布出去,于是我的问题来啦,怎么把这些JS文件搞成一个啊,群里有个 朋友告诉我,GruntJS – JavaScript多文件编译,风格检查,链接与压缩神器.Google了一 把终于帮我完成这个任务,算是入门,分享一下过程. 一什么是GruntJS 不想翻译英文,自己看它的网站吧->http://gruntjs.com/

  • java使用TimerTask定时器获取指定网络数据

    复制代码 代码如下: import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.URL;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Timer;import java.util.TimerTask; public class GetYinInf

  • 使用GruntJS构建Web程序之Tasks(任务)篇

    如何自定义Grunt任务 有时我们需要写一些自己的grunt任务,下面是一个具体例子 一.准备 1. 新建一个目录g12. 新建package.json,放入g13. 新建Gruntfile.js,放入g1 package.json 复制代码 代码如下: {    "name": "g1",    "version": "0.1.0",    "author": "@snandy",  

  • dos进程查看与停止命令分享(tasklist,taskkill)

    dos进程查看与停止命令 查看进程: tasklist /svc 停止进程: taskkill /im 2468 /f DOS下查看进程,结束进程命令(黑客常用命令) shall时想给对方上传一个木马什么的,总会遇一些东西的阻止,有些可以停止服务呀什么的,但有些东西只能关闭,这个时候我们就要用两个命令了,首先是tasklist 查看进程例表,在dos下直接输入tasklist就会例出所运行的所有进程,如我的电脑. 在例表中每一个进程都有一个相对的PID值,我们只要把相对的PID值kill掉就OK

  • SC tasklist与ntsd命令使用

    CMD中以启动messenger为例: sc config messenger start= auto net start messenger auto 自动 demand 手动 disabled 禁用 以上第一句为把手动改为自动,=号后面的空格不能漏,第二句是开启服务命令 CMD中有查看进程的命令 tasklist TASKLIST [/S system [/U username [/P [password]]]] [/M [module] | /SVC | /V] [/FI filter]

  • 配置Grunt的Task时通配符支持和动态生成文件名问题

    copy: { // 这是Task里的其中一个Target dests: { expand: true, cwd: '<%=config.app%>/newFolder', src: ['**/{a*,b*}.html'], dest: '<%=config.dist%>/newFolder', ext: ".shtml", extDot: "first", flatten:true, //去掉中间上当,下面的rename可以再找回来 ren

  • android异步任务设计思详解(AsyncTask)

    这里说有设计思想是我根据查看Android源代码提炼出来的代码逻辑,所以不会跟Google工程师的原始设计思想100%符合(也有可能是0%),但是本文一定可以帮助你理解AsyncTask,也可能有一些你以前没有发现的内容. 大家都知道,Android的主线程(又叫UI线程,线程ID为1)有一些限制策略,使得主线程有些事做不了,比如访问网络就不允许,否则就是报,但在2.3之后的版本,你可以通过添加以下代码更改其限制策略,从而强制使得主线程可以访问网络: 复制代码 代码如下: if (android

  • AsyncTask陷阱之:Handler,Looper与MessageQueue的详解

    AsyncTask的隐蔽陷阱先来看一个实例这个例子很简单,展示了AsyncTask的一种极端用法,挺怪的. 复制代码 代码如下: public class AsyncTaskTrapActivity extends Activity {    private SimpleAsyncTask asynctask;    private Looper myLooper;    private TextView status; @Override    public void onCreate(Bun

  • java多线程返回值使用示例(callable与futuretask)

    Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子 复制代码 代码如下: package com.future.test; import java.io.FileNotFoundException;import java.io.IOException;i

  • Android开发笔记之:AsyncTask的应用详解

    AsyncTask的介绍及基本使用方法关于AsyncTask的介绍和基本使用方法可以参考官方文档和<Android开发笔记之:深入理解多线程AsyncTask>这里就不重复.AsyncTask引发的一个问题上周遇到了一个极其诡异的问题,一个小功能从网络上下载一个图片,然后放到ImageView中,是用AsyncTask来实现的,本身逻辑也很简单,仅是在doInBackground中用HTTP请求把图片的输入流取出,然后用BitmapFactory去解析,然后再把得到的Bitmap放到Image

  • win2003下杀任何进程的命令(taskkill,ntsd)

    1.ntsd.exe 怎么才能关掉一个用任务管理器关不了的进程?大多数人想到的都是专门工具,如IceSword.其实用Windows自带的工具就能杀大部分进程: c:\>ntsd -cq -p PID 只有System.SMSS.EXE和CSRSS.EXE不能杀.前两个是纯内核态的,最后那个是Win32子系统,ntsd本身需要它.ntsd从2000开始就是系统自带的用户态调试工具.被调试器附着(attach)的进程会随调试器一起退出, 所以可以用来在命令行下终止进程.使用ntsd自动就获得了de

  • 利用ace的ACE_Task等类实现线程池的方法详解

    本代码应该是ace自带的例子了,但是我觉得是非常好的,于是给大家分享一下.注释非常详细啊.头文件 复制代码 代码如下: #ifndef THREAD_POOL_H#define THREAD_POOL_H/* In order to implement a thread pool, we have to have an object that   can create a thread.  The ACE_Task<> is the basis for doing just   such a

  • 使用GruntJS构建Web程序之合并压缩篇

    有如下步骤: 1.新建项目Bejs2.新建文件package.json3.新建文件Gruntfile.js4.命令行执行grunt任务 一.新建项目Bejs源码放在src下,该目录有两个子目录asset和js.js下放selector.js和ajax.js,这在上一篇已经讲了如何合并压缩它们.这篇只关注asset目录,asset目录下放了一些图片和css文件.一会会把两个css资源reset.css和style.css合并,压缩到dest/asset目录.一个合并版本all.css,一个压缩版本

随机推荐