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

.NET中的async和await关键字使用及Task异步调用实例的更多相关文章

  1. C# 中的Async 和 Await 的用法详解

    众所周知C#提供Async和Await关键字来实现异步编程.在本文中,我们将共同探讨并介绍什么是Async 和 Await,以及如何在C#中使用Async 和 Await. 同样本文的内容也大多是翻译 ...

  2. C# 中的 Async 和 Await

    这篇文章由Filip Ekberg为DNC杂志编写. 自跟随着.NET 4.5 及Visual Studio 2012的C# 5.0起,我们能够使用涉及到async和await关键字的新的异步模式.有 ...

  3. [译] C# 5.0 中的 Async 和 Await (整理中...)

    C# 5.0 中的 Async 和 Await [博主]反骨仔 [本文]http://www.cnblogs.com/liqingwen/p/6069062.html 伴随着 .NET 4.5 和 V ...

  4. 浅谈async、await关键字 => 深谈async、await关键字

    前言 之前写过有关异步的文章,对这方面一直比较弱,感觉还是不太理解,于是会花点时间去好好学习这一块,我们由浅入深,文中若有叙述不稳妥之处,还请批评指正. 话题 (1)是不是将方法用async关键字标识 ...

  5. 在MVC中使用async和await的说明

    首先,在mvc中如果要用纯异步请不要使用async和await,可以直接使用Task.Run. 其次,在mvc中使用async和await可以让系统开新线程处理Task的代码,同时不必等Task执行结 ...

  6. Async 与 Await 关键字研究

    1        Aynsc 和 Await 关键字的研究 在 .NET 4.0 以后,基于 Task 的异步编程模式大行其道,因其大大简化了异步编程所带来的大量代码工作而深受编程人员的欢迎,如果你曾 ...

  7. async和await关键字实现异步编程

    async和await关键字实现异步编程 异步编程   概念 异步编程核心为异步操作,该操作一旦启动将在一段时间内完成.所谓异步,关键是实现了两点:(1)正在执行的此操作,不会阻塞原来的线程(2)一旦 ...

  8. 为什么我们要使用Async、Await关键字

    前不久,在工作中由于默认(xihuan)使用Async.Await关键字受到了很多质问,所以由此引发这篇博文“为什么我们要用Async/Await关键字”,请听下面分解: Async/Await关键字 ...

  9. ES2017 中的 Async 和 Await

    ES2017 在 6 月最终敲定了,随之而来的是广泛的支持了我最喜欢的最喜欢的JavaScript功能: async(异步) 函数.如果你也曾为异步 Javascript 而头疼,那么这个就是为你设计 ...

随机推荐

  1. Sketch插件新利器——使用Mockplus DS制作设计规范

    Sketch,作为一款专为图标和界面设计而打造的优质矢量绘图工具,也是设计师们制作和完善公司企业内部设计规范系统不可或缺的设计工具. 然而,逐个导出和上传Sketch编辑优化的设计系统资源费时而费力. ...

  2. css控制两个表格的边线重合

    控制两个表格的边线重合 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http:/ ...

  3. 不要怂,就是GAN (生成式对抗网络) (一): GAN 简介

    前面我们用 TensorFlow 写了简单的 cifar10 分类的代码,得到还不错的结果,下面我们来研究一下生成式对抗网络 GAN,并且用 TensorFlow 代码实现. 自从 Ian Goodf ...

  4. ffmpeg只编译h264

    ./configure --arch=arm --cross-prefix=arm-none-linux-gnueabi- --extra-ldflags=-static --target-os=li ...

  5. Mybatis的select查询的三种方式

    1.首先建立一个测试的dao public interface IStudentDao { // 根据姓名查询 List<Student> selectStudentsByName(Str ...

  6. Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果

    目前Android的实现是:有来电时,音乐声音直接停止,铃声直接直接使用设置的铃声音量进行铃声播放. Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果. 如果 ...

  7. 2、Docker和虚拟机的对比

    2.1 虚拟化技术   虚拟机Virtual Machine与容器化技术(代表Docker)都是虚拟化技术,两者的区别在于虚拟化的程度不同.   Docker为代表的容器化技术并不是虚拟机.   虚拟 ...

  8. CAS实战の获取多用户信息

    先列出版本号: 服务端版本:cas server 4.0.0 客户端版本:cas client 3.3.3 cas server step1:先将primaryPrincipalResolver be ...

  9. windows本地代码上传github

    1.下载Git工具 https://www.git-scm.com/download/win 2.进到项目目录 cd /dir/dir/dir/file 3.初始化 git init 4.添加远程仓库 ...

  10. Unity3d中使用Lua

    对于手机游戏,如果可以在线更新以实现bug修复.新功能添加等等,其好处自不必多说. 通过C#的反射机制,也可以实现某种程度上的脚本级更新,具体可以参考 http://docs.unity3d.com/ ...