多线程事儿(task)之 一(转载)
此文转载作为记录,转载地址https://www.cnblogs.com/xiaoXuZhi/p/XYH_tsak_one.html
多线程,一个多么熟悉的词汇,作为一名程序员,我相信无论是从事什么开发语言,都能够轻轻松松说出几种实现多线程的方式,并且在实际工作种也一定用到过多线程,比如:定时器、异步作业等等,如果你说你没有用过多线程,我怀疑你是不是一名程序员,哈哈。
哈哈,言归正传,今天我们要说说c#中的多线线程哪一些事,当然c#在实现多线程上有多种方式,比如:Threads、Action、ThreadPool、Task、Parallel等,当然每一种方式都用其优点和缺点,也有其应用场景,在此不一一说明,今天我们主要以task为例,来一起聊聊task的使用,也就当着一次自我总结提炼罢了。
为什么要用多线程
其实我们在实际使用过程中,使用多线程的目的其实即使为了实现异步+并行,异步:是相对同步的,同步就是一个流程安装一个流程执行完毕,异步就是在不影响主流程的执行同时,可以执行其他流程,这也就是达到了几个逻辑并行执行的效果。当然了,不是说异步就完全是独立执行,相互间就没有关联关系,其实在异步的同时,也可以在特定节点等待阻塞等待异步结果啦。说了半天废话,不要走开,主题才刚刚开始,下面以实际例子来演绎task的实际使用吧!
如何创建和运行一个task
微软就是那么6逼,在创建和执行一个task时,都给大家提供了多种方式来实现,大家可以根据其具体的使用场景和习惯来选择最适合的方式,下面通过代码来简单说明如下的三种实现方式:

/// <summary>
/// 简单的task创建方式演示
/// </summary>
private static void TaskCreatFun()
{
// 其一、通过传统的 new 方式来实例化一个task对象,这种方式需要手动通过start来启动
Task newTask = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Hello Engineer, 我是 new 的一个task,线程ID:Thread.CurrentThread.ManagedThreadId}");
}); // 启动 tsak
newTask.Start(); // 其二、通过工厂 factory 来生成一个task对象,并自启动
Task factoryTask = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Hello Engineer, 我是 factory 生产 的一个task,线程ID:Thread.CurrentThread.ManagedThreadId}");
}); // 其三、通过 Task.Run(Action action) 来创建一个自启动task
Task runTask = Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Hello Engineer, 我是 Task.Run 创建一个自启动task,线程ID:Thread.CurrentThread.ManagedThreadId}");
});
runTask.RunSynchronously(); Console.WriteLine($"Hello Engineer, 我是主线程啦!线程ID{Thread.CurrentThread.ManagedThreadId}");
}

代码的执行结果:很容易看出,task执行阻塞主线程,并且几个task并行执行。
如何创建一个带有返回值的task
在上面的代码实例种,我们以不同的方式创建并运行了一个多线程,但是这几个task都是没有返回值的,这样的task适用于:独立执行的一个子业务,完全不关心其执行结果。但是我们在实际使中,往往会需要关系其执行结果的。
以一个实际的业务场景来说明:比如,我们在一个酒店预订系统中,需要实时到不同的第三接口实时查询某一个酒店的某一客房在最新状态,比如有3个接口渠道:携程、艺龙、去哪儿,该如何实现呢?
首先,如果我们采用串行的方式一个一个的去取数据,那样估计你的系统慢到不会有人用的,所以我们第一个想到的是,采用task来并行获取,并返回获取到的结果值,下面简单模拟一下代码实现:

/// <summary>
/// 获取最新的客房信息
/// </summary>
/// <returns>客房信息集合</returns>
private static List<string> GetHotelRoomInfro()
{
// 模拟存储获取到的酒店客房数据集合
List<string> listHotelRoomInfro = new List<string>(); Console.WriteLine("下面通过3个task,并行的到不同接口方获取实时的客房信息:");
Console.WriteLine(""); // 在此我也分别对3种不同渠道,采用3种不同的方式来实现 // 其一、通过传统的 new 方式来实例化一个task对象,获取 携程 的客房数据
Task<string> newCtripTask = new Task<string>(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
Console.WriteLine("携程 内部处理完毕!");
return "我是来自 携程 的最新客房信息";
}); // 启动 tsak
newCtripTask.Start(); // 其二、通过工厂 factory 来生成一个task对象,并自启动:获取 艺龙 的客房数据
Task<string> factoryElongTask = Task<string>.Factory.StartNew(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
Console.WriteLine("艺龙 内部处理完毕!");
return "我是来自 艺龙 的最新客房信息";
}); // 其三、通过 Task.Run(Action action) 来创建一个自启动task:获取 去哪儿网 的客房数据
Task<string> runQunarTask = Task<string>.Run(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
Console.WriteLine("去哪儿网 内部处理完毕!");
return "我是来自 去哪儿网 的最新客房信息";
}); // 分别打印不同渠道的客房数据
Console.WriteLine(newCtripTask.Result);
Console.WriteLine(factoryElongTask.Result);
Console.WriteLine(runQunarTask.Result); Console.WriteLine("");
Console.WriteLine("所有接口方的最新客房数据获取完毕!");
return listHotelRoomInfro;
}

执行结果:
其实通过上面的执行结果,我们不难看出以下几点
1、task虽然是异步线程,但是可以有返回值的
2、task的返回值获取方式为:task.Result
3、在通过task.Result获取返回值时,会阻塞主线程,其实也不难理解,你要等待处理结果,肯定会阻塞等待啦!
task可以同步执行吗?
通过上面的实际代码测试,我们知道task都是异步执行,那么有人会问,task可以实现同步执行吗?不急,强大的微软也想到了这个问题,于是乎,提供了 task.RunSynchronously() 来同步执行,但是这种方式执行,只有通过new 实例化的task才有效,原因也很简单,其他两种方式创建task都已经自启动执行了,不可能在来一个同步启动执行吧,嘿嘿。下面我们用代码来演示:

/// <summary>
/// 通过RunSynchronously 实现task的同步执行
/// </summary>
private static void TaskRunSynchronously()
{
Console.WriteLine("主线程开始执行!"); Task<string> task = new Task<string>(() =>
{
Thread.Sleep(100);
Console.WriteLine("Task执行结束!");
return "";
}); /// task.Start();
/// task.Wait(); // 获取执行结果,会阻塞主流程
// string result = task.Result; //同步执行,task会阻塞主线程
task.RunSynchronously(); Console.WriteLine("执行主线程结束!");
Console.ReadKey();
}

执行结果:很明显主线程阻塞等待task同步执行。
task同步执行,出了上面的实现方式,其实我们也可以通过task.wait()来变相的实现同步执行效果,当然也可以用task.Result来变现的实现,原理很简单,因为wait()和Result都是要阻塞主流程,直到task执行完毕,是不是有异曲同工之妙呢!以代码为例:
通过task.wait()实现,只需要对上面的代码做一个简单的调整,如下:其最终的效果一样:

/// <summary>
/// 通过RunSynchronously 实现task的同步执行
/// </summary>
private static void TaskRunSynchronously()
{
Console.WriteLine("主线程开始执行!"); Task<string> task = new Task<string>(() =>
{
Thread.Sleep(100);
Console.WriteLine("Task执行结束!");
return "";
}); task.Start();
task.Wait(); // 获取执行结果,会阻塞主流程
// string result = task.Result; // 同步执行,task会阻塞主线程
// task.RunSynchronously(); Console.WriteLine("执行主线程结束!");
Console.ReadKey();
}

执行结果:
通过task.Result 实现,前提是task一定要有返回值,如下:其最终的效果一样:

/// <summary>
/// 通过RunSynchronously 实现task的同步执行
/// </summary>
private static void TaskRunSynchronously()
{
Console.WriteLine("主线程开始执行!"); Task<string> task = new Task<string>(() =>
{
Thread.Sleep(100);
Console.WriteLine("Task执行结束!");
return "";
}); task.Start();
/// task.Wait(); // 获取执行结果,会阻塞主流程
string result = task.Result; // 同步执行,task会阻塞主线程
// task.RunSynchronously(); Console.WriteLine("执行主线程结束!");
Console.ReadKey();
}

执行效果也和上面的两种方式一样。
当然我还可以通过task.IsCompleted来变现实现,在此就不在细说,简单一个代码示意即可:while (!task.IsCompleted){}
当然我上面说的几种实现同步的方式,只是为了拓展一下思路,不一定都是最优方案。
Task的Wait、WaitAny、WaitAll方法介绍
task的基本创建和用法,上面都做了简单的介绍,但是在我们实际业务场景中,往往不是那么简单的单纯实现。比如:还是刚刚上面的那个酒店信息获取为例,现在新的需求是:3个渠道的接口实时数据,我们只需要获取到其中的一个就立即返回会用户,避免用户等待太久,那么这个时候task.WaitAny就派上用场了,WaitAny就是等待一组tsak集合中,只要有一个执行完毕就不在等待,与之对应的是WaitAll需要等待一组tsak集合中所有tsak都执行完毕,当然了Wait是针对一个task的,等待本身执行完成,上面的模拟同步执行已经说了,就不在啰嗦。

/// <summary>
/// 获取最新的客房信息(只需要获取到一个即可)
/// </summary>
/// <returns>客房信息集合</returns>
private static List<string> GetOneHotelRoomInfro()
{
// 模拟存储获取到的酒店客房数据集合
List<string> listHotelRoomInfro = new List<string>(); Console.WriteLine("下面通过3个task,并行的到不同接口方获取实时的客房信息:");
Console.WriteLine(""); // 在此我也分别对3种不同渠道,采用3种不同的方式来实现 // 其一、通过传统的 new 方式来实例化一个task对象,获取 携程 的客房数据
Task newCtripTask = new Task(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是来自 携程 的最新客房信息");
}); // 启动 tsak
newCtripTask.Start(); // 其二、通过工厂 factory 来生成一个task对象,并自启动:获取 艺龙 的客房数据
Task factoryElongTask = Task.Factory.StartNew(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是来自 艺龙 的最新客房信息");
}); // 其三、通过 Task.Run(Action action) 来创建一个自启动task:获取 去哪儿网 的客房数据
Task runQunarTask = Task.Run(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是来自 去哪儿网 的最新客房信息");
}); // 只需要等待一个有返回即可
Task.WaitAny(new Task[] { newCtripTask, factoryElongTask, runQunarTask }); // 等待所有接口数据返回
// Task.WaitAll(new Task[] { newCtripTask, factoryElongTask, runQunarTask }); Console.WriteLine("已经有接口数据返回!");
foreach (var item in listHotelRoomInfro)
{
Console.WriteLine($"返回的接口数据为:{item}");
} Console.WriteLine("主线程执行完毕!");
Console.ReadKey(); Console.WriteLine("");
return listHotelRoomInfro;
}

上面是演示了WaitAny的执行结果,至于WaitAll就不在演示了,一看便知
好了,今天时间差不多了,就介绍到儿了,明天我们在来研究:
1、task的延续操作(WhenAny/WhenAll/ContinueWith)
2、任务取消(CancellationTokenSource)
3、实际案例分析
多线程事儿(task)之 一(转载)的更多相关文章
- 【多线程】 Task ,async ,await
[多线程]Task ,async ,await 一. WinForm 里经常会用到多线程, 多线程的好出就不多说了,来说说多线程比较麻烦的地方 1. UI 线程与其他线程的同步,主要是 Form 和 ...
- 【多线程】 Task
[多线程] Task 一. 常用方法: 1. ContinueWith : 当前 Task 完成后, 执行传入的 Task 2. Delay : 创建一个等待的 Task,只有在调用 Wait 方法时 ...
- .Net 多线程 (1) Task
多线程是一种有效提高程序工作效率的方法.当然为了效率需要使用更多的cpu,内存等资源. 并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机,如果串行,一个队列使用一台咖啡机,那么哪怕前 ...
- 【多线程】Task
介绍 Task是.NET推出数据任务处理的工作类.位于System.Threading.Tasks命名空间下,通过命名空间也可以看出是个多线程类. 创建Task: Task有很多构造函数,无参有参都有 ...
- java多线程学习--java.util.concurrent (转载)
题记:util和concurrent 包是后续重点先看的和学习的模块 原文地址:http://www.cnblogs.com/sunhan/p/3817806.html CountDownLatch, ...
- 多线程(4)Task
使用线程池使得创建线程已经很简单了,但是使用线程池不支持线程的取消,完成和失败通知等交互操作,为了解决这些问题,.net 4.0带来了TPL(Task Parallel Library)任务并行库,下 ...
- C#多线程编程のTask(任务全面解析)
Task是.NET4.0加入的,跟线程池ThreadPool的功能类似,用Task开启新任务时,会从线程池中调用线程,而Thread每次实例化都会创建一个新的线程. 我们可以说Task是一种基于任务的 ...
- C#多线程中的异常处理(转载)
常规Thread中处理异常 使用Thread创建的子线程,需要在委托中捕捉,无法在上下文线程中捕捉 static void Main(string[] args) { ThreadStart thre ...
- java多线程面试题(来自转载)
在典型的Java面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程, 如何创建线程,用什么方式创建线程比较好(比如:继承thread类还是调用Runnable接口),然后逐渐问到并发问 ...
随机推荐
- Kubernetes弹性伸缩全场景解读(五) - 定时伸缩组件发布与开源
前言 容器技术的发展让软件交付和运维变得更加标准化.轻量化.自动化.这使得动态调整负载的容量变成一件非常简单的事情.在kubernetes中,通常只需要修改对应的replicas数目即可完成.当负载的 ...
- Python多版本pip安装库的问题
引 机器上总是会有Python2.7的版本和Python3.x的版本,今天接触到一台服务器上面有Python2.7和Python3.4,想在Python3.4下安装一个TensorFlow,但不管怎么 ...
- Hive高阶聚合函数 GROUPING SETS、Cube、Rollup
-- GROUPING SETS作为GROUP BY的子句,允许开发人员在GROUP BY语句后面指定多个统计选项,可以简单理解为多条group by语句通过union all把查询结果聚合起来结合起 ...
- 将Eclipse中文注释字体变大方法
今天下了最新的eclipse玩,结果发现注释变得灰常小,差点看瞎哥24K氪金狗眼 于是在网上找了找解决方法,结果都不对 最后自己试出来了... 方法: Window --> Preferenc ...
- javascript 容易混淆遗忘的基础知识
1. 标识符 所谓标识符,就是指变量.函数.属性的名字,或者函数的参数.标识符可以是按照下列格式规则组合起来的一或多个字符: 1.1 第一个字符必须是一个字母.下划线( _ )或 ...
- hdu 1254 推箱子(嵌套搜索,bfs中有dfs)
推箱子 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submiss ...
- <climits>头文件
<climits>头文件定义的符号常量 CHAR_MIN char的最小值SCHAR_MAX signed char 最大值SCHAR_MIN signed char 最小值UCHAR_ ...
- ubuntu snmp 安装与配置
0.说明 关于一个完整的教程,还是那句话,国内的要么不完整,要么就太旧了,而且思路也不清晰,所以这里写一篇完整的给大家分享一下. 虽然对于Linux主机的监控可以通过执行特定的命令来完成,但是相比之后 ...
- 几种常见排序算法的基本介绍,性能分析,和c语言实现
本文介绍6种常见的排序算法,以及他们的原理,性能分析和c语言实现: 为了能够条理清楚,本文所有的算法和解释全部按照升序排序进行 首先准备一个元素无序的数组arr[],数组的长度为length,一个交换 ...
- P1026 翻硬币
题目描述 小明正在玩一个"翻硬币"的游戏.桌上放着排成一排的若干硬币.我们用 * 表示正面,用 o 表示反面(是小写字母,不是零). 比如,可能情形是:**oo***oooo 如果 ...