Asp.Net Core 轻松学-多线程之Task快速上手
前言
Task是从 .NET Framework 4 开始引入的一项基于队列的异步任务(TAP)模式,从 .NET Framework 4.5 开始,任何使用 async/await 进行修饰的方法,都会被认为是一个异步方法;实际上,这些异步方法都是基于队列的线程任务,从你开始使用 Task 去运行一段代码的时候,实际上就相当于开启了一个线程,默认情况下,这个线程数由线程池 ThreadPool 进行管理的。
1. Task 的使用方法
Task 的使用用方法非常简单,一行代码就可以开始一个异步任务
1.1 最简单的使用方式
static void EasyTask()
{
// 执行一个无返回值的任务
Task.Run(() =>
{
Console.WriteLine("runing...");
});
// 执行一个返回 int 类型结果的任务
Task.Run<int>(() =>
{
return new Random().Next();
});
// 声明一个任务,仅声明,不执行
Task t = new Task(() =>
{
Console.WriteLine("");
});
}
上面的代码看起来非常简单,只需要一行代码就完成了一个异步任务线程,先不要去深究其背后的原理,对于新手来说,先解决能用,再去了解为什么可以这样使用,不然,一开始就失去了学习的信心
2.1 使用 TaskFactory 工厂开始异步任务
static void Factory()
{
List<Task<int>> tasks = new List<Task<int>>();
TaskFactory factory = new TaskFactory();
tasks.Add(factory.StartNew<int>(() =>
{
return 1;
}));
tasks.Add(factory.StartNew<int>(() =>
{
return 2;
}));
foreach (var t in tasks)
{
Console.WriteLine("Task:{0}", t.Result);
}
}
上面的代码使用 TaskFactory 创建并运行了两个异步任务,同时把这两个任务加入了任务列表 tasks 中,然后立即迭代此 tasks 获取异步任务的执行结果,使用 TaskFactory 工厂类,可以创建一组人物,然后依次执行它们
2.3 执行上面的代码,输出结果如下
3. 处理 Task 中的异常
异步任务中发生异常会导致任务抛出 TaskCancelException 的异常,仅表示任务退出,程序应当捕获该异常;然后,立即调用 Task 进行状态判断,获取内部异常
3.1 模拟抛出异常
static void SimpleTask()
{
var task = Task.Run(() =>
{
Console.WriteLine("SimpleTask");
Task.Delay(1000).Wait();
throw new Exception("SimpleTask Error");
});
try
{
task.Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
if (task.IsCompletedSuccessfully)
{
Console.WriteLine("IsCompleted");
}
}
上面的代码模拟了 Task 内部发生的异常,并捕获了异常,通常情况下,推荐使用 Task 的任务状态判断以进行下一步的任务处理(如果需要),如果仅仅是简单的执行一个异步任务,直接捕获异常即可,这里使用了状态判断,如果任务已完成,则打印一则消息:IsCompleted;很明显,在上面的代码中,此 “IsCompleted” 消息并不会被打印到控制台 注意,这里使用了 task.IsCompletedSuccessfully 而不是 task.IsCompleted,这两者的区别在于,前者只有在任务正常执行完成,无异常,无中途退出指令的情况下才会表示已完成,而 task.IsCompleted 则仅仅表示“任务完成”
3.2 执行程序,输出结果
4. 同步上下文
在 WinForm/WPF 应用程序中,也常常需要在 UI 上开辟异步任务,通常情况下,窗体控件仅允许创建其的线程访问,在没有 Task 的时代,处理异步上下文到同步上下文是一件非常复杂的事情,在 Task 出现以后,提供了 TaskScheduler 任务调度器,让我们可以非常方便的在异步线程中访问 UI 线程的资源
4.1 获取当前线程上下文对象
static void TaskSynchronizationContext()
{
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
var t1 = Task.Factory.StartNew<int>(() =>
{
return 1;
});
t1.ContinueWith((atnt) =>
{
// 从这里访问 UI 线程的资源
Console.WriteLine("从这里访问 UI 线程的资源");
}, UISyncContext);
}
从上面的代码可以发现,仅仅需要调用 TaskScheduler.FromCurrentSynchronizationContext() 获得当前线程的同步上下文,然后在执行异步任务的时候传入,即可访问当前线程创建的 UI 资源
5. Task 的运行方式
5.1 基于 ThreadPool 线程池的方式
一个异步任务总是处于队列中,任务队列基于先进先出的原则,最新进入队列的任务总是最先被执行;但是,在多线程的环境下,最先执行并不意味着最先结束,意识到这一点很重要,每个任务可调度的资源和处理的进度决定了任务的完成时间。 默认情况下,所有的任务都使用 ThreadPool 的资源,当你开启一个 Task 的时候,实际上,是由 ThreadPool 分配了一个线程,ThreadPool 的上限取决于很多方面的因素,例如虚拟内存的大小,当 Task 开启的数量超过ThreadPool 的上限的时候,Task 将进入排队状态,可以手动设置 ThreadPool 的大小
static void SetThreadPool()
{
var available = ThreadPool.SetMaxThreads(8, 16);
Console.WriteLine("Result:{0}", available);
}
上面的代码表示设置当前程序可使用的线程池大小,但是,SetMaxThreads 的值不应该小于托管服务器的 CPU 核心数量,否则,变量 available 的值将显示为 false,表示未成功设置线程池上限 注意:ThreadPool 上的所有线程都是后台线程,也就是说,其IsBackground属性是true,在托管程序退出后,ThreadPool 也将会退出。
5.2 长时间运行于后台的任务
在创建 Task 的时候,我们可能需要做一些长时间运行的业务,这个时候如果使用默认的 ThreadPool 资源,在并发状态下,这是不合适的,因为该任务总是长时间的占用线程池中的资源,导致线程池数量受限,这种情况下,可以在创建任务的时候使用指定 TaskCreationOptions.LongRunning 方式创建 Task
static void LongTask()
{
Task.Factory.StartNew(() =>
{
Console.WriteLine("LongRunning Task");
}, TaskCreationOptions.LongRunning);
}
上面的代码看起来和创建普通的 Task 任务并没有多大的区别,唯一不同的是,在参数中传入了 TaskCreationOptions.LongRunning,指定这个是一个 LongRunning 类型的任务,当TaskFactory 收到这样一个类型的任务时,将会为这个任务开辟一个独立的线程,而不是从 ThreadPool 中创建
6. 有条件的 Task
Task 内部提供多种多样的基于队列的链式任务管理方法,通过使用这些快捷方式,可以让异步队列有序的执行,比如ContinueWith(),ContinueWhenAll(),ContinueWhenAny(),WaitAll(),WaitAny(),WhenAll(),WhenAny()
6.1 使用演示
static void WithTask()
{
var order1 = Task.Run(() =>
{
Console.WriteLine("Order 1");
});
// 匿名委托将等待 order1 执行完成后执行,并将 order1 对象作为参数传入
order1.ContinueWith((task) =>
{
Console.WriteLine("Order 1 Is Completed");
});
var t1 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t1"); });
var t2 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t2"); });
var t3 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t3"); });
Task.WaitAll(t1, t2, t3);
// t1,t2,t3 完成后输出下面的消息
Console.WriteLine("t1,t2,t3 Is Complete");
var t4 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t4"); });
var t5 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t5"); });
var t6 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t6"); });
Task.WaitAny(t4, t5, t6);
// 当任意任务完成时,输出下面的消息,目前按延迟时间计算,在 t4 完成后立即输出下面的信息
Console.WriteLine("t4,t5,t6 Is Complete");
var t7 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t7"); });
var t8 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t8"); });
var t9 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t9"); });
var whenAll = Task.WhenAll(t7, t8, t9);
// WhenAll 不会等待,所以这里必须显示指定等待
whenAll.Wait();
// 当所有任务完成时,输出下面的消息
Console.WriteLine("t7,t8,t9 Is Complete");
var t10 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t10"); });
var t11 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t11"); });
var t12 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t12"); });
var whenAny = Task.WhenAll(t10, t11, t12);
// whenAny 不会等待,所以这里必须显示指定等待
whenAny.Wait();
// 当任意任务完成时,输出下面的消息,目前按延迟时间计算,在 t10 完成后立即输出下面的信息
Console.WriteLine("t10,t11,t12 Is Complete");
}
6.2 执行上面的代码,输出结果如下
值得注意的是,当调用 WhenAll 方法时,会返回执行任务的状态,此状态是所有任务的统一状态,如果执行了 3 个任务,而其中一个出错,则返回任务状态表示为:Faulted,如果任意任务被取消,则状态为:Canceled; 当调用 WhenAny() 方法时,表示任意任务完成即可表示完成,此时,会返回最先完成的任务信息 注意:WhenAll 和 WhenAny 方法正常执行,无异常,无取消,则所返回的完成状态表示为:RanToCompletion
结束语
- 本章简要介绍了基于队列的异步任务(TAP)使用方式
- 介绍了TAP 运行的方式、以及异常处理
- 同时还介绍了如何使用 UI 线程同步上下文对象,以及有条件使用 TAP 的各种方法
示例代码下载
https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.TaskDemo
- 数据库进程间通信解决方案
- 【实践操作】在iPhone上创建你的第一个机器学习模型
- WP8微信5.3开始内测 支持Cortana语音 两微破冰了?
- 数据库进程间通信解决方案之MQ
- Extjs4.2 rest 与webapi数据交互----顺便请教了程序员的路该怎么走
- 这或许是对小白最友好的python入门了吧——7,组织列表
- 这或许是对小白最友好的python入门了吧——6,删除列表元素
- 集群开源软件赏:JGroups
- 看到他我一下子就悟了---委托
- 这或许是对小白最友好的python入门了吧——5,修改和添加列表元素
- 这或许是对小白最友好的python入门了吧——4,列表
- 【深度学习】自动驾驶:使用深度学习预测汽车的转向角度
- 这或许是对小白最友好的python入门了吧——3,数字处理
- 数据库恢复方案
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux 系统双网卡绑定配置实现
- Linux系统设置开机自动运行脚本的方法实例
- Linux中fuser命令用法详解
- 在Ubuntu中实现人脸识别登录的完整步骤
- Linux下如何寻找相同文件的方法
- CentOS 7中Nginx日志定时拆分实现过程详解
- 浅谈linux模拟多线程崩溃和多进程崩溃
- Linux下MongoDB的安装和配置教程
- Linux配置实现免密钥登录过程解析
- 可以提高效率的十个Linux命令别名汇总
- 基于linux命令提取文件夹内特定文件路径
- Ubuntu20.04修改ip地址的方法示例
- Linux 逻辑卷管理(LVM)使用方法总结
- Linux 下载安装VSCode 使用编程输出当前时间的方法
- 详解Linux获取线程的PID(TID、LWP)的几种方式