C#多线程编程(4)--异常处理+前三篇的总结
本来是打算讲并行For和PLINQ的,但是我感觉前三篇我没有讲得很清晰。之前一直在看《CLR via C#》(后文简称CLR)的多线程部分,其中有些部分不是很明白,今天翻开《果壳中的C#》(后文简称果壳),看了下多线程部分,发现这本书讲的内容虽然很少,但是提纲挈领,把我之前读CLR中的知识点都串了起来。之前讲关键字async,await时,提到了状态机。其实,await会被编译成awaiter.GetAwaiter()方法,以及之后的委托,果壳中有很简单的例子来讲解,让我茅塞顿开,还有其他的部分也是这样。因此我决定写个总结,就是把之前我讲的我认为还没讲透的地方换种方式再讲一遍,目的是让大家,也是我自己真正的明白多线程的工作原理以及如何更好的使用异步。
在总结之前,我要先介绍一下多线程的异常处理。多线程的异常处理分两种:非池化线程(自己new出来的线程)和池化线程(调用Task)。我们先来看一个非池化的例子。
static void Main(string[] args)
{
try
{
Go();
}
catch (NullReferenceException exception)
{
//代码永远执行不到这里
}
}
static void Go()
{
new Thread(() =>{
Thread.Sleep();
throw new NullReferenceException();
}).Start();
}
上述代码中,程序永远不会执行到catch里,因为当前try catch只能捕获到主线程中的异常,无法捕获其他线程中的异常。处理办法是将异常处理部分放到Go()函数中。
在池化线程中,任务中抛出的异常都会被捕获,并收集到AggregateException中,例子如下
static void Main(string[] args)
{
Go();
Console.ReadLine();
}
static void Go()
{
try { Task.Run(() => throw new NullReferenceException()).Wait(); }
catch (AggregateException ex){
if (ex.InnerException is NullReferenceException)
Console.WriteLine("捕获异常");
}
}
若你用的是vs2013或者更低版本,当运行这段代码时,会弹出异常提示,再次点击运行,就能看到“捕获异常”,vs2015和vs2017则不需要,这是因为VS将遇到的异常弹出,是为了方便调试。这个例子可以看到任务中抛出了NullReferenceException异常,并在catch块中捕获了该异常。可以注意到异常的类型是AggregateException,当任务中抛出了多个异常,会存放在InnerExceptions中,这个和InnerException不同,这是一个只读集合,里面存放的是全部的异常。上述代码中,若不显示调用wait()方法,则不会捕获到异常。只有等待任务,或者尝试获取任务的返回值时,线程池才会抛出异常列表中的第一个异常。
接下来是总结,说是总结,其实是将前面未讲透的知识点仔细讲解一下。
先说一下async和await关键字。
static void Main(string[] args){
GoAsync();
Console.WriteLine("异步运行");
Console.ReadLine();
}
static async void GoAsync(){
await Task.Run(() => {
//模拟其他任务
Thread.Sleep(); });
Console.WriteLine("任务结束");
}
可以看到程序运行了GoAsync()后,直接打印出了“异步运行”四个字,然后过了大约2秒才打印出任务结束。这表明GoAsync方法为异步方法,它不会阻塞线程,就是程序在执行到该函数后,不需要等待该方法结束,而是直接继续执行下一行代码。可以注意到GoAsync()方法标有async,且在Task.Run前添加了await关键字,这表明程序会等待Task任务,知道该任务结束后,才会继续执行。其实,这段代码会被编译器翻译成大概下面代码的样子(我省略了绝大部分代码,只保留关键的部分,若有兴趣,可以将该段代码编译后,调用IL反编译器,查看编译器真正的编译结果)。
static void Main(string[] args){
GoAsync();
Console.WriteLine("异步运行");
Console.ReadLine();
}
static void GoAsync(){
var awaiter = Task.Run(() => {
//模拟其他任务
Thread.Sleep(); }).GetAwaiter();
awaiter.OnCompleted(() => Console.WriteLine("任务结束"));
}
调用await关键字,相当于在此处获取该任务的awaiter,该awaiter在任务结束后,会调用传入到OnCompleted方法中的委托。可以看到上述的写法没有async和await关键字优美,且没有办法标识GoAsync()方法为异步,只能以名字区分。async关键字能够很清晰的表明该方法是异步方法,且await用法简单,只要放在想要等待的任务前面就可以了,编译器会把await关键后面的部分放入到awaiter.OnCompleted()里面,等到任务结束后再开始执行。
下面来介绍下TaskCompletionSource,该类型是用来实现线程的返回值问题的。TaskCompleteSource的结构大概是这样的:
public class TaskCompletionSource<TResult>{
public void SetResult(TResult result);
public void SetException(Exception ex);
public void SetCancel();
public bool TrySetException(Exception ex);
...
}
每个Set方法都只能调用一次,再次调用会抛出异常,而Try方法会返回false。
下面就利用TaskCompletionSource来实现我们自己的Run()方法:
static void Main(string[] args)
{
GoAsync();
Console.WriteLine("异步运行");
Console.ReadLine();
}
static async void GoAsync()
{
var t = await Run(() =>
{
//模拟其他任务
Thread.Sleep();
return "任务完成";
});
Console.WriteLine(t);
}
//自己的Run方法
static Task<TResult> Run<TResult>(Func<TResult> func)
{
var tcs = new TaskCompletionSource<TResult>();
ThreadPool.QueueUserWorkItem(t =>
{
try
{
tcs.SetResult(func());
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, null);
return tcs.Task;
}
可以看到,Run()方法中,通过tcs.setResult()方法,成功的将返回值抛了出来,并返回了含有结果的Task。该段代码和上面的调用Task.Run的GoAsync()方法一样。也可以使用TaskCompletionSource加上定时器来实现Task.Delay()方法,而不用显式调用线程。该方法的实现就留给读者自行完成。
以上,本文介绍了多线程中异常的捕获和处理,其中分为非池化线程和池化线程两种,其中非池化的异常处理要放在待执行的方法中,而池化线程可以通过调用Result或者await来将异常统一存放到AggregateException中统一处理。然后我针对前面三篇文章中没有讲透的点重新讲解了一下。包括await关键字的机制,编译器是通过Awaiter.OnComplete来实现的。之后是TaskCompletionSource,该类型是用来实现线程的返回值问题的,也讲解了如何实现自己的Task.Run()方法。并给读者留了一个用TaskCompletionSource和定时器来实现Task.Delay()的练手题。
欢迎大家在我的评论区与我交流。
C#多线程编程(4)--异常处理+前三篇的总结的更多相关文章
- Java多线程编程实战指南(核心篇)读书笔记(三)
(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76686044冷血之心的博客) 博主准备恶补一番Java高并发编程相 ...
- Java多线程编程实战指南(核心篇)读书笔记(五)
(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76730459冷血之心的博客) 博主准备恶补一番Java高并发编程相 ...
- Java多线程编程实战指南(核心篇)读书笔记(四)
(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76690961冷血之心的博客) 博主准备恶补一番Java高并发编程相 ...
- Java多线程编程实战指南(核心篇)读书笔记(二)
(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76651408冷血之心的博客) 博主准备恶补一番Java高并发编程相 ...
- Java多线程编程实战指南(核心篇)读书笔记(一)
(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76422930冷血之心的博客) 博主准备恶补一番Java高并发编程相 ...
- 《Java多线程编程实战指南(核心篇)》阅读笔记
<Java多线程编程实战指南(核心篇)>阅读笔记 */--> <Java多线程编程实战指南(核心篇)>阅读笔记 Table of Contents 1. 线程概念 1.1 ...
- jq最新前三篇文章高亮显示
/*---------最新前三篇文章高亮显示-------------*/ function latest(){ var color_arr=new Array( "blue", ...
- 学习笔记《Java多线程编程实战指南》三
3.1串行.并发与并行 1.串行:一件事做完接着做下一件事. 2.并发:几件事情交替进行,统筹资源. 3.并行:几件事情同时进行,齐头并进,各自运行直到结束. 多线程编程的实质就是将任务处理方式由串行 ...
- 【Windows编程】系列第三篇:文本字符输出
上一篇我们展示了如何使用Windows SDK创建基本控件,本篇来讨论如何输出文本字符. 在使用Win32编程时,我们常常要输出文本到窗口上,Windows所有的文本字符或者图形输出都是通过图形设备接 ...
随机推荐
- SSE图像算法优化系列十七:一些图像处理中常用小过程的SSE实现。
在做图像处理的SSE优化时,也会经常遇到一些小的过程.数值优化等代码,本文分享一些个人收藏或实现的代码片段给大家. 一.快速求对数运算 对数运算在图像处理中也是个经常会遇到的过程,特备是在一些数据压缩 ...
- Linux知识体系之路径属性与目录
最近在看鸟哥的Linux私房菜,我觉得这本书还是很不错的.这里进行相关的总结. 1.Linux目录权限概念 Linux一般讲目录可存取的方式分为三个类别,分别是owner/group/other, ...
- Jenkins配置备份恢复插件ThinBackup
一.系统管理-管理插件-找到ThinBackup并安装 二.系统管理-找到ThinBackup-点击Setting进行设置 第一个参数备份目录是必选,其它可选,点保存. 三.保存后返回到ThinBac ...
- JavaScript 中的对象深度复制(Object Deep Clone)
JavaScript中并没有直接提供对象复制(Object Clone)的方法. JavaScript中的赋值,其实并不是复制对象,而是类似`c/c++`中的引用(或指针),因此下面的代码中改变对象b ...
- 【深度学习系列】用PaddlePaddle进行车牌识别(一)
小伙伴们,终于到了实战部分了!今天给大家带来的项目是用PaddlePaddle进行车牌识别.车牌识别其实属于比较常见的图像识别的项目了,目前也属于比较成熟的应用,大多数老牌厂家能做到准确率99%+.传 ...
- pycharm的用法
Ctrl / 注释(取消注释)选择的行 Shift + Enter开始新行Ctrl + Enter智能换行TAB Shift+TAB缩进/取消缩进所选择的行Ctrl + Alt + I自动缩进行Ctr ...
- chromedriver与chrome版本映射表(最新)
selenium想在chrome进行跑,前提需要下载chromedriver,以下整理了chromedriver与chrome的对应关系表 chromedriver(下载地址):http://chro ...
- Tomcat8.5.24日志自动清理(maxDays)功能探究
前言 测试人员反馈tomcat目录下的日志占用空间很大,需要自动清理.接到这个反馈时,想象着应该是一个很简单的功能,tomcat应该已经实现了日志的自动清理功能.于是乎,我先到网上查询了如何自动清除t ...
- WEB服务器防盗链_HttpAccessKeyModule_Referer(Nginx&&PHP)
盗链的概念指在自己的页面上展示一些并不在自己服务器上的内容.也就是获得他人服务器上的资源地址,绕过别人的资源展示页面,直接在自己的页面上向最终用户提供此内容.如,小站盗用大站的图片.音乐.视频.软件等 ...
- 震撼功能:逐浪CMS全面支持PWA移动生成意指未来
Progressive Web App, 简称 PWA,是提升 Web App 的体验的一种新方法,能给用户原生应用的体验. PWA 能做到原生应用的体验不是靠特指某一项技术,而是经过应用一些新技术进 ...