Ø  前言

C# Asynchronous Programming(异步编程)有几种实现方式,其中 Asynchronous Method(异步方法)就是其中的一种。异步方法是 C#5.0 才有的新特性,主要采用 async、await 关键字声明为异步方法,完成对方法的异步调用。C#5.0 对应的 VS 版本是 VS2012,对应的 .NET Framework 版本是 v4.5,所以需要在此基础上才支持。(否则可能报:找不到“async”修饰符所需的所有类型。目标框架版本是否不正确,或者缺少对程序集的引用?)

Ø  什么是异步方法

1.   异步方法,是指在执行当前方法的同时,可以异步的去调用其他方法(异步方法),并且不会阻塞当前方法的线程。

2.   使用了 async 修饰符的方法称为异步方法,通常配合 await 运算符和 Task 异步任务一起使用。

1)   如果方法使用了 async 修饰符,则方法中需要包含一个以上 await 运算符,否则将以同步执行。

2)   反之,如果方法中包含一个以上 await 运算符,则必须声明为一个异步方法,即使用 async 修饰符。

3.   Task 分为两种:

1)   Task,表示可以执行一个异步操作,声明如下:

public class Task : IAsyncResult, IDisposable { }

2)   Task<TResult>,表示可以执行带有返回值的异步操作,声明如下:

public class Task<TResult> : Task { }

4.   异步方法的返回类型必须为 void、Task、Task<TResult> 中的其中一种。

1)   void,表示无返回值,不关心异步方法执行后的结果,一般用于仅仅执行某一项任务,但是不关心结果的场景。

2)   Task,表示异步方法将返回一个 Task 对象,该对象通常用于判断异步任务是否已经完成,可以使用 taskObj.Wait() 方法等待,或者 taskObj.IsCompleted 判断。

3)   Task<TResult>,表示异步方法将返回一个 Task<TResult> 对象,该对象的 Result 属性则是异步方法的执行结果,调用该属性时将阻塞当前线程(异步方法未执行完成时)。

Ø  归纳一下:void 不关心结果;Task 只关心是否执行完成;Task<TResult> 不止关心是否执行完成,还要获取执行结果。

Ø  下面通过几个生活中比较形象的例子来理解异步方法的使用

1.   模拟扔垃圾(不关心结果,返回 void 类型)

/// <summary>

/// 扔垃圾。

/// </summary>

public void DropLitter()

{

WriteLine("老婆开始打扫房间,线程Id为:{0}", GetThreadId());

WriteLine("垃圾满了,快去扔垃圾");

CommandDropLitter();

WriteLine("不管他继续打扫,线程Id为:{0}", GetThreadId());

Thread.Sleep(100);

WriteLine("老婆把房间打扫好了,线程Id为:{0}", GetThreadId());

}

/// <summary>

/// 通知我去扔垃圾。

/// </summary>

public async void CommandDropLitter()

{

WriteLine("这时我准备去扔垃圾,线程Id为:{0}", GetThreadId());

await Task.Run(() =>

{

WriteLine("屁颠屁颠的去扔垃圾,线程Id为:{0}", GetThreadId());

Thread.Sleep(1000);

});

WriteLine("垃圾扔了还有啥吩咐,线程Id为:{0}", GetThreadId());

}

运行以上代码:

以上代码在 CommandDropLitter() 方法上加了 async 修饰符,并且使用 await 运算符开启了一个新的 Task 去执行另一个任务。注意:当前线程遇到 await 时,则立刻跳回调用方法继续往下执行。而 Task 执行完成之后将执行 await 之后的代码,并且与 await 之前的线程不是同一个。

2.   模拟打开电源开关(关心是否执行完成,返回 Task 类型)

/// <summary>

/// 打开电源开关。

/// </summary>

public void OpenMainsSwitch()

{

WriteLine("我和老婆正在看电视,线程Id为:{0}", GetThreadId());

WriteLine("突然停电了,快去看下是不是跳闸了");

Task task = CommandOpenMainsSwitch();

WriteLine("没电了先玩会儿手机吧,线程Id为:{0}", GetThreadId());

Thread.Sleep(100);

WriteLine("手机也没电了只等电源打开,线程Id为:{0}", GetThreadId());

//task.Wait();    //所以这里将被阻塞,直到任务完成

//或者

while (!task.IsCompleted) { Thread.Sleep(100); }

WriteLine("又有电了我们继续看电视,线程Id为:{0}", GetThreadId());

}

/// <summary>

/// 通知我去打开电源开关。

/// </summary>

public async Task CommandOpenMainsSwitch()

{

WriteLine("这时我准备去打开电源开关,线程Id为:{0}", GetThreadId());

await Task.Run(() =>

{

WriteLine("屁颠屁颠的去打开电源开关,线程Id为:{0}", GetThreadId());

Thread.Sleep(1000);

});

WriteLine("电源开关打开了,线程Id为:{0}", GetThreadId());

}

运行以上代码:

1)   可见,调用 Wait() 方法后,当前线程被阻塞了,直到 Task 执行完成后,当前线程才继续执行。

2)   注意:由于 CommandOpenMainsSwitch() 是一个异步方法,虽然返回类型为 Task 类型,但是在我们代码中并没有写(也不能写) return task 语句,这是为什么呢?可能是这种返回类型比较特殊,或者编译器自动帮我们完成了吧!就算写也只能写 return 语句,后面不能跟对象表达式。

3.   模拟去买盐(不止关心是否执行完成,还要获取执行结果。返回 Task<TResult> 类型)

/// <summary>

/// 做饭。

/// </summary>

public void CookDinner()

{

WriteLine("老婆开始做饭,线程Id为:{0}", GetThreadId());

WriteLine("哎呀,没盐了");

Task<string> task = CommandBuySalt();

WriteLine("不管他继续炒菜,线程Id为:{0}", GetThreadId());

Thread.Sleep(100);

string result = task.Result;    //必须要用盐了,等我把盐回来(停止炒菜(阻塞线程))

WriteLine("用了盐炒的菜就是好吃【{0}】,线程Id为:{1}", result, GetThreadId());

WriteLine("老婆把饭做好了,线程Id为:{0}", GetThreadId());

}

/// <summary>

/// 通知我去买盐。

/// </summary>

public async Task<string> CommandBuySalt()

{

WriteLine("这时我准备去买盐了,线程Id为:{0}", GetThreadId());

string result = await Task.Run(() =>

{

WriteLine("屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());

Thread.Sleep(1000);

return "盐买回来了,顺便我还买了一包烟";

});

WriteLine("{0},线程Id为:{1}", result, GetThreadId());

return result;

}

运行以上代码:

1)   以上代码 task.Result 会阻塞当前线程,与 task.Wait() 类似。

2)   注意:与前面返回类型为 Task 的 CommandOpenMainsSwitch() 方法一样,虽然 CommandBuySalt() 方法返回类型为 Task<string>,但是我们的返回语句是 return 字符串。

Ø  其他示例

1.   在前面(模拟去买盐)的示例中,异步方法中只开启了一个 Task,如果开启多个 Task 又是什么情况,看代码:

public void AsyncTest()

{

WriteLine("AsyncTest() 方法开始执行,线程Id为:{0}", GetThreadId());

Task task = Test1();

WriteLine("AsyncTest() 方法继续执行,线程Id为:{0}", GetThreadId());

task.Wait();

WriteLine("AsyncTest() 方法结束执行,线程Id为:{0}", GetThreadId());

}

public async Task Test1()

{

WriteLine("Test1() 方法开始执行,线程Id为:{0}", GetThreadId());

await Task.Factory.StartNew((state) =>

{

WriteLine("Test1() 方法中的 {0} 开始执行,线程Id为:{1}", state, GetThreadId());

Thread.Sleep(1000);

WriteLine("Test1() 方法中的 {0} 结束执行,线程Id为:{1}", state, GetThreadId());

}, "task1");

await Task.Factory.StartNew((state) =>

{

WriteLine("Test1() 方法中的 {0} 开始执行,线程Id为:{1}", state, GetThreadId());

Thread.Sleep(3000);

WriteLine("Test1() 方法中的 {0} 结束执行,线程Id为:{1}", state, GetThreadId());

}, "task2");

WriteLine("Test1() 方法结束执行,线程Id为:{0}", GetThreadId());

}

运行以上代码:

当异步方法中有多个 await 时,会依次执行所有的 Task,只有当所有 Task 执行完成后才表示异步方法执行完成,当前线程才得以执行。

2.   同样以前面(模拟去买盐)的示例,如果发现其实家里还有盐,这是就要告诉我不用买了(取消异步操作),怎么实现?这就要借助 System.Threading.CancellationTokenSource 和 System.Threading.Tasks.CancellationToken 对象来完成。

/// <summary>

/// 做饭(买盐任务取消)。

/// </summary>

public void CookDinner_CancelBuySalt()

{

WriteLine("老婆开始做饭,线程Id为:{0}", GetThreadId());

WriteLine("哎呀,没盐了");

CancellationTokenSource source = new CancellationTokenSource();

Task<string> task = CommandBuySalt_CancelBuySalt(source.Token);

WriteLine("不管他继续炒菜,线程Id为:{0}", GetThreadId());

Thread.Sleep(100);

string result = "家里的盐";

if (!string.IsNullOrEmpty(result))

{

source.Cancel();    //传达取消请求

WriteLine("家里还有盐不用买啦,线程Id为:{0}", GetThreadId());

}

else

{

//如果已取消就不能再获得结果了(否则将抛出 System.Threading.Tasks.TaskCanceledException 异常)

//你都叫我不要买了,我拿什么给你?

result = task.Result;

}

WriteLine("既然有盐我就继续炒菜【{0}】,线程Id为:{1}", result, GetThreadId());

WriteLine("老婆把饭做好了,线程Id为:{0}", GetThreadId());

WriteLine("最终的任务状态是:{0},已完成:{1},已取消:{2},已失败:{3}",

task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);

}

/// <summary>

/// 通知我去买盐(又告诉我不用买了)。

/// </summary>

public async Task<string> CommandBuySalt_CancelBuySalt(CancellationToken token)

{

WriteLine("这时我准备去买盐了,线程Id为:{0}", GetThreadId());

//已开始执行的任务不能被取消

string result = await Task.Run(() =>

{

WriteLine("屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());

Thread.Sleep(1000);

}, token).ContinueWith((t) =>  //若没有取消就继续执行

{

WriteLine("盐已经买好了,线程Id为:{0}", GetThreadId());

Thread.Sleep(1000);

return "盐买回来了,顺便我还买了一包烟";

}, token);

WriteLine("{0},线程Id为:{1}", result, GetThreadId());

return result;

}

运行以上代码:

1)   刚开始我以为调用 source.Cancel() 方法后会立即取消 Task 的执行,仔细一想也不太可能。如果需要在 Task 执行前或者执行期间完成取消操作,我们自己写代码判断 cancellationToken.IsCancellationRequested 属性是否为 true(该属性在调用 source.Cancel() 后或者 source.CancelAfter() 方法到达指定时间后为 true),如果为 true 结束执行即可。

2)   这里所说的“传达取消请求”的意思是,每个 Task 在执行之前都会检查 cancellationToken.IsCancellationRequested 属性是否为 true,如果为 true 则不执行 Task,并将设置 Status、IsCompleted、IsCanceled 等。

3)   所以,在 Task 的源码中有这样一段代码

if (cancellationToken.IsCancellationRequested)

{

// Fast path for an already-canceled cancellationToken

this.InternalCancel(false);

}

4)   官网示例:CancellationTokenSource 类 (System.Threading)

3.   乘热打铁,我们再来看看多个 CancellationTokenSource 取消异步任务,以及注册取消后的回调委托方法,继续以(模拟去买盐)为例:

/// <summary>

/// 做饭(多个消息传达买盐任务取消)。

/// </summary>

public void CookDinner_MultiCancelBuySalt()

{

WriteLine("老婆开始做饭,线程Id为:{0}", GetThreadId());

WriteLine("哎呀,没盐了");

CancellationTokenSource source1 = new CancellationTokenSource();    //因为存在而取消

CancellationTokenSource source2 = new CancellationTokenSource();    //因为放弃而取消

CancellationTokenSource source = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);

//注册取消时的回调委托

source1.Token.Register(() =>

{

WriteLine("这是因为{0}所以取消,线程Id为:{1}", "家里还有盐", GetThreadId());

});

source2.Token.Register((state) =>

{

WriteLine("这是因为{0}所以取消,线程Id为:{1}", state, GetThreadId());

}, "不做了出去吃");

source.Token.Register((state) =>

{

WriteLine("这是因为{0}所以取消,线程Id为:{1}", state, GetThreadId());

}, "没理由");

//这里必须传递 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象

Task<string> task = CommandBuySalt_MultiCancelBuySalt(source.Token);

WriteLine("等等,好像不用买了,线程Id为:{0}", GetThreadId());

Thread.Sleep(100);

string[] results = new string[] { "家里的盐", "不做了出去吃", "没理由" };

Random r = new Random();

switch (r.Next(1, 4))

{

case 1:

source1.Cancel();           //传达取消请求(家里有盐)

//source1.CancelAfter(3000);  //3s后才调用取消的回调方法

WriteLine("既然有盐我就继续炒菜【{0}】,线程Id为:{1}", results[0], GetThreadId());

break;

case 2:

source2.Cancel();           //传达取消请求(不做了出去吃)

//source2.CancelAfter(3000);  //3s后才调用取消的回调方法

WriteLine("我们出去吃不用买啦【{0}】,线程Id为:{1}", results[1], GetThreadId());

break;

case 3:

source.Cancel();            //传达取消请求(没理由)

//source.CancelAfter(3000);   //3s后才调用取消的回调方法

WriteLine("没理由就是不用买啦【{0}】,线程Id为:{1}", results[2], GetThreadId());

break;

}

WriteLine("最终的任务状态是:{0},已完成:{1},已取消:{2},已失败:{3}",

task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);

}

/// <summary>

/// 通知我去买盐(又告诉我不用买了,各种理由)。

/// </summary>

public async Task<string> CommandBuySalt_MultiCancelBuySalt(CancellationToken token)

{

WriteLine("这时我准备去买盐了,线程Id为:{0}", GetThreadId());

//已开始执行的任务不能被取消

string result = await Task.Run(() =>

{

WriteLine("屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());

Thread.Sleep(1000);

}, token).ContinueWith((t) =>  //若没有取消就继续执行

{

WriteLine("盐已经买好了,线程Id为:{0}", GetThreadId());

Thread.Sleep(1000);

return "盐买回来了,顺便我还买了一包烟";

}, token);

WriteLine("{0},线程Id为:{1}", result, GetThreadId());

return result;

}

运行以上代码:

1)   当调用 source.Cancel() 方法后,会立即取消并调用 token 注册的回调方法;而调用 existSource.CancelAfter() 方法则会等到达指定的毫秒数后才会取消。

2)   注意:传递给异步方法的 token 对象,必须是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象,否则取消将无效。

3)   回调的委托方法始终只有两个,一个是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象的注册委托,另一个是调用 Cancel()/CancelAfter() 方法的 Token 对象的注册委托。

4)   如果以上代码调用的是 CancelAfter(3000) 方法,运行结果如下:

C# 异步方法(AM)的更多相关文章

  1. .Net Core MVC 网站开发(Ninesky) 2.4、添加栏目与异步方法

    在2.3中完成依赖注入后,这次主要实现栏目的添加功能.按照前面思路栏目有三种类型,常规栏目即可以添加子栏目也可以选择是否添加内容,内容又可以分文章或其他类型,所以还要添加一个模块功能.这次主要实现栏目 ...

  2. [C#] 走进异步编程的世界 - 剖析异步方法(上)

    走进异步编程的世界 - 剖析异步方法(上) 序 这是上篇<走进异步编程的世界 - 开始接触 async/await 异步编程>(入门)的第二章内容,主要是与大家共同深入探讨下异步方法. 本 ...

  3. [C#] 走进异步编程的世界 - 剖析异步方法(下)

    走进异步编程的世界 - 剖析异步方法(下) 序 感谢大家的支持,这是昨天发布<走进异步编程的世界 - 剖析异步方法(上)>的补充篇. 目录 异常处理 在调用方法中同步等待任务 在异步方法中 ...

  4. 异步方法的意义何在,Async和await以及Task的爱恨情仇,还有多线程那一家子。

    前两天刚感受了下泛型接口的in和out,昨天就开始感受神奇的异步方法Async/await,当然顺路也看了眼多线程那几个.其实多线程异步相关的类单个用法和理解都不算困难,但是异步方法Async/awa ...

  5. 又踩.NET Core的坑:在同步方法中调用异步方法Wait时发生死锁(deadlock)

    之前在将 Memcached 客户端 EnyimMemcached 迁移 .NET Core 时被这个“坑”坑的刻骨铭心(详见以下链接),当时以为只是在构造函数中调用异步方法(注:这里的异步方法都是指 ...

  6. 异步方法不能使用ref和out的解决方法

    异常处理汇总-后端系列:http://www.cnblogs.com/dunitian/p/4523006.html 应用场景==>后端现在都是用异步方法,那么分页是必不可少的,于是就有了这个问 ...

  7. C#异步方法的使用

    from:http://www.myext.cn/csharp/a_6765.html 也许业内很多高不成低不就的程序员都会对一些知识点会有些迷惑,原因是平常工作用的少,所以也就决定了你对这个事物的了 ...

  8. 【Spring】利用Spring最简单地使用异步方法

    有时候我们想异步地调用某个方法. 比如这个场景:在业务处理完毕后,需给用户发送通知邮件.由于邮件发送需调用邮箱服务商,有可能发生阻塞,我们就可以异步调用.当然有个前提,即如果邮件发送失败,不需要提示用 ...

  9. C# 中 async/await 调用传统 Begin/End 异步方法

    最近在改进园子的图片上传程序,希望实现用户上传图片时同时将图片文件保存在三个地方:1)服务器本地硬盘:2)又拍云:3)阿里云OSS.并且在保存时使用异步操作. 对于异步保存到本地硬盘,只需用 Stea ...

  10. 【Win10】单元测试中捕获异步方法的指定异常

    温馨提醒:本文需要知道什么是单元测试才能阅读. 在之前 WPF.ASP.NET 中,单元测试要捕捉指定异常,我们是通过 ExpectedExceptionAttribute 来实现的.如下图: 但是, ...

随机推荐

  1. [luogu2476][bzoj1079][SCOI2008]着色方案【动态规划】

    题目描述 有n个木块排成一行,从左到右依次编号为1~n.你有k种颜色的油漆,其中第i种颜色的油漆足够涂ci个木块.所有油漆刚好足够涂满所有木块,即c1+c2+-+ck=n.相邻两个木块涂相同色显得很难 ...

  2. rt-thread之串口设备的配置流程

    @2019-01-30 [小记] > rt-thread 工程启动之后先是进入函数 rtthread_startup 做一些系统运行前的基础工作,主要有: * 板级硬件初始化 * 系统定时器初始 ...

  3. 【dfs】p1451 求细胞数量

    题目描述 一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数.(1<=m,n<=100)? 输入输出格式## ...

  4. LOJ#6277. 数列分块入门 1

    分块思想,先把原来的序列分成根号n快,然后对于更新的部分,先操作这个序列边上的部分,然后再中间部分整块操作,这样复杂度就是O(根号N) #include<map> #include< ...

  5. 按奇偶排序数组 II

    题目描述 给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数. 对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数:当 A[i] 为偶数时, i 也是偶数. 你可以返回任何满足上述 ...

  6. 使用ss命令对tcp连接数和状态的监控性能优化

    之前对tcp的监控采用netstat命令,发现在服务器繁忙的时候效果不理想,这个命令占用大量的cpu有时候高达90%以上,可能会导致业务的不稳定,所以改用ss命令对脚本进行优化 对tcp连接数和状态的 ...

  7. (二)flask-sqlalchemy基本操作

    对数据库基本操作 在Flask-SQLAlchemy中,插入.修改.删除操作,均由数据库会话管理. 会话用 db.session 表示.在准备把数据写入数据库前,要先将数据添加到会话中然后调用 db. ...

  8. 区块链使用Java,以太坊 Ethereum, web3j, Spring Boot

    Blockchain is one of the buzzwords in IT world during some last months. This term is related to cryp ...

  9. height、clientHeight、offsetHeight、scrollHeight、height()、 innerHeight()、outerHeight()等的区别

    1.height height是css属性,这个属性定义元素内容区的高度,在内容区外面可以增加内边距.边框和外边距. 当  box-sizing: content-box 时,高度应用到元素的内容框. ...

  10. django跨域请求问题

    一 同源策略 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响.可以说Web是构建在同源策略基础之 ...