多线程,一个多么熟悉的词汇,作为一名程序员,我相信无论是从事什么开发语言,都能够轻轻松松说出几种实现多线程的方式,并且在实际工作种也一定用到过多线程,比如:定时器、异步作业等等,如果你说你没有用过多线程,我怀疑你是不是一名程序员,哈哈。

哈哈,言归正传,今天我们要说说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();
Console.WriteLine($"Hello Engineer, 我是 new 的一个task,线程ID:Thread.CurrentThread.ManagedThreadId}");
}); // 启动 tsak
newTask.Start(); // 其二、通过工厂 factory 来生成一个task对象,并自启动
Task factoryTask = Task.Factory.StartNew(() =>
{
Thread.Sleep();
Console.WriteLine($"Hello Engineer, 我是 factory 生产 的一个task,线程ID:Thread.CurrentThread.ManagedThreadId}");
}); // 其三、通过 Task.Run(Action action) 来创建一个自启动task
Task runTask = Task.Run(() =>
{
Thread.Sleep();
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(, ));
Console.WriteLine("携程 内部处理完毕!");
return "我是来自 携程 的最新客房信息";
}); // 启动 tsak
newCtripTask.Start(); // 其二、通过工厂 factory 来生成一个task对象,并自启动:获取 艺龙 的客房数据
Task<string> factoryElongTask = Task<string>.Factory.StartNew(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(, ));
Console.WriteLine("艺龙 内部处理完毕!");
return "我是来自 艺龙 的最新客房信息";
}); // 其三、通过 Task.Run(Action action) 来创建一个自启动task:获取 去哪儿网 的客房数据
Task<string> runQunarTask = Task<string>.Run(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(, ));
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();
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();
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();
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(, ));
listHotelRoomInfro.Add("我是来自 携程 的最新客房信息");
}); // 启动 tsak
newCtripTask.Start(); // 其二、通过工厂 factory 来生成一个task对象,并自启动:获取 艺龙 的客房数据
Task factoryElongTask = Task.Factory.StartNew(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(, ));
listHotelRoomInfro.Add("我是来自 艺龙 的最新客房信息");
}); // 其三、通过 Task.Run(Action action) 来创建一个自启动task:获取 去哪儿网 的客房数据
Task runQunarTask = Task.Run(() =>
{
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(, ));
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)之 二 延续操作

 第三篇:聊聊多线程那一些事儿(task)之 三 异步取消和异步方法

 第四篇:聊聊多线程那一些事儿 之 四 经典应用(取与舍、动态创建)

END
为了更高的交流,欢迎大家关注我的公众号,扫描下面二维码即可关注,谢谢:

聊聊多线程哪一些事儿(task)之 一的更多相关文章

  1. 聊聊多线程哪一些事儿(task)之 二 延续操作

    hello,又见面啦,昨天我们简单的介绍了如何去创建和运行一个task.如何实现task的同步执行.如何阻塞等待task集合的执行完毕等待,昨天讲的是task的最基本的知识点,如果你没有看昨天的博客, ...

  2. 聊聊多线程那一些事儿(task)之 三 异步取消和异步方法

    hello,咋们又见面啦,通过前面两篇文章的介绍,对task的创建.运行.阻塞.同步.延续操作等都有了很好的认识和使用,结合实际的场景介绍,这样一来在实际的工作中也能够解决很大一部分的关于多线程的业务 ...

  3. 聊聊多线程哪一些事儿(task)之 三 异步取消和异步方法

    hello,咋们又见面啦,通过前面两篇文章的介绍,对task的创建.运行.阻塞.同步.延续操作等都有了很好的认识和使用,结合实际的场景介绍,这样一来在实际的工作中也能够解决很大一部分的关于多线程的业务 ...

  4. 聊聊多线程那一些事儿 之 五 async.await深度剖析

     hello task,咱们又见面啦!!是不是觉得很熟读的开场白,哈哈你哟这感觉那就对了,说明你已经阅读过了我总结的前面4篇关于task的文章,谢谢支持!感觉不熟悉的也没有关系,在文章末尾我会列出前四 ...

  5. 聊聊RabbitMQ那一些事儿之一基础应用

    聊聊RabbitMQ那一些事儿之一基础应用 Hi,各位热爱技术的小伙伴您们好,今年的疫情害人啊,真心祝愿您和您的家人大家都平平安安,健健康康.年前到现在一直没有总结点东西,写点东西,不然久了自己感觉自 ...

  6. 多线程随笔二(Task)

    Task类是.net 4.0新加进来的特性,对原有的Thread,ThreadPool做了进一步的封装,使得.net平台上的多线程编程变得更加方便.废话不多说,进入正题. 一. Task启动 Task ...

  7. 多线程系列(3)任务Task

    虽然使用线程池ThreadPool让我们使用多线程变得容易,但是因为是由系统来分配的,如果想对线程做精细的控制就不太容易了,比如某个线程结束后执行一个回调方法.恰好Task可以实现这样的需求.这篇文章 ...

  8. 多线程之旅(Task 任务)

    一.Task(任务)和ThreadPool(线程池)不同       源码 1.线程(Thread)是创建并发工具的底层类,但是在前几篇文章中我们介绍了Thread的特点,和实例.可以很明显发现局限性 ...

  9. [深入学习C#]C#实现多线程的方式:Task——任务

    简介 .NET 4包含新名称空间System.Threading.Tasks,它 包含的类抽象出了线程功能. 在后台使用ThreadPool. 任务表示应完成的某个单元的工作. 这个单元的工作可以在单 ...

随机推荐

  1. 【转载】Ubuntu终端常用的快捷键

    Ubuntu中的许多操作在终端(Terminal)中十分的快捷,记住一些快捷键的操作更得心应手.在Ubuntu中打开终端的快捷键是Ctrl+Alt+T.其他的一些常用的快捷键如下: 快捷键 功能 Ta ...

  2. 利用idea构建hibernate

    1.创建项目 若勾选Use library,则点击右侧的Create,使用本地已下载的Hibernate 5.2.13框架(必须导入hibernate-release-5.2.13.Final\lib ...

  3. Flask_Migrate数据库迁移

    migrate数据库迁移 有models,没有迁移仓库.本地新建数据库:首次创建迁移仓库.迁移脚本:执行迁移脚本生成数据库表: python manage.py db init python mana ...

  4. ArcGIS 如何设置地图显示范围大小

    说来惭愧,学ArcGIS也已经有两年了.今天才知道原来ArcGIS是可以设置地图显示范围大小的 打开ArcMap,选择左边图例的图层(Layers) ,右键点击,选择属性(Properties..), ...

  5. 阿里云POLARDB如何助力轻松筹打造5亿用户信赖的大病筹款平台?

    轻松筹首创了“大病救助”模式,帮助了众多病患在第一时间解決了医疗资金等问题,为了从源头解决了医疗资金问题.而在轻松筹这样全球5.5亿用户信赖的大病筹款平台的背后,是日益增长的各种数据.面对这样数据量所 ...

  6. 2018-2-13-win10-uwp-如何让WebView标识win10手机

    title author date CreateTime categories win10 uwp 如何让WebView标识win10手机 lindexi 2018-2-13 17:23:3 +080 ...

  7. include 语句中使用双引号与括号有什么区别?

    Include 的语法 你在学习如何构造函数时,看到了不同的 include 语句: # include <iostream> # include "distance.h&quo ...

  8. Python基础:常用函数

    1:enumerate enumerate(sequence, start=0) 该函数返回一个enumerate对象(一个迭代器).其中的sequence参数可以是序列.迭代器或者支持迭代的其他对象 ...

  9. OpenStack组件系列☞glance简介

    Glance项目提供虚拟机镜像的发现,注册,取得服务. Glance提供restful API可以查询虚拟机镜像的metadata,并且可以获得镜像. 通过Glance,虚拟机镜像可以被存储到多种存储 ...

  10. hdu 2312 Cliff Climbing (pfs)

    Problem - 2312 一条很暴力,有点恶心的搜索.题意其实很简单,主要是pfs的时候拓展结点会有种麻烦的感觉.注意的是,这里的n和m跟平常见到的有所不同,交换过来了.我的代码就是在因为这个长宽 ...