C#多线程之任务的用法详解
作者:Ruby_Lu 发布时间:2023-08-27 10:51:18
Parallel类(https://www.jb51.net/article/244267.htm)的并行任务需要结束后才能运行后面的代码,如果想不等结束后在开始动作,可以使用Task类更好地控制并行动作。
任务表示应完成的某个工作单元。这个工作单元可以在单独的线程中运行,也可以以同步方式启动一个任务,这需要等待主调线程。使用任务不仅可以获得一个抽象层,还可以对底层线程进行很多控制。
任务相对Parallel类提供了非常大的灵活性。例如,可以定义连续的工作——在一个任务完成后该执行什么工作。这可以根据任务成功与否来分。还可以在层次结构中安排任务。例如,父任务可以创建新的子任务。
一.启动任务
要启动任务,可以使用TaskFactory类或Task类的构造函数和Start()方法。Task类的构造函数在创建任务上灵活性比较大。
在启动任务时,会创建Task类的一个实例,利用Action或Action<T>委托(不带参数或带一个参数),可以指定应运行的代码。
1.使用线程池的任务
线程池提供了一个后台线程的池(后面详细介绍了线程池)。线程池独自管理线程,根据需要增加或减少线程池中的线程数。线程池中的线程用于实现一些动作,之后仍然返回线程池中。
下面介绍创建线程池的任务的四种方法:
先定义一个要调用使用的方法:
//避免写入控制台的操作交叉,这里使用lock关键字同步
static object taskMethodLock = new object();
static void TaskMethod(object title)
{
lock (taskMethodLock)
{
Console.WriteLine(title);
Console.WriteLine("task id:{0},thread:{1}",Task.CurrentId,Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("is pooled thread:{0}",Thread.CurrentThread.IsThreadPoolThread);
Console.WriteLine("is background thread:{0}",Thread.CurrentThread.IsBackground);
}
}
(1).使用实例化的TaskFactory类,把TaskMethod方法和TaskMethod方法的参数传递给StartNew方法:
var tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod,"using a task factory");
(2).使用Task类的静态属性Factory来访问TaskFactory,以调用StartNew()方法。类似第一种,也使用了工厂,但对工厂的控制没那么全面。
Task t2 = Task.Factory.StartNew(TaskMethod,"using factory via a task");
(3).使用Task的构造函数。实例化Task对象时任务不会执行,只是指定Created状态。接着调用Start()方法,启动任务。
Task t3 = new Task(TaskMethod,"using a task constructor and Start");
t3.Start();
(4).直接调用Task类的Run()方法启动任务。Run()方法没有传递带参数委托的版本,可以通过传递lambda表达式。
Task t4 = Task.Run(()=> TaskMethod("using Run method"));
static void Main(string[] args)
{
var tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod,"using a task factory");
Task t2 = Task.Factory.StartNew(TaskMethod,"using factory via a task");
Task t3 = new Task(TaskMethod,"using a task constructor and Start");
t3.Start();
Task t4 = Task.Run(()=> TaskMethod("using Run method"));
Console.ReadKey();
}
2.同步任务
任务不一定使用线程池中的线程,也可以使用其它线程。任务也可以同步运行,以相同的线程作为主调线程。
示例:
static void RunSyncTask()
{
TaskMethod("main thread");
var t = new Task(TaskMethod,"run sync");
t.RunSynchronously();
}
输出:
上面代码先在主线程上直接调用TaskMethod方法,然后在创建的Task上调用。从输出看到,主线程是一个前台线程,没有任务ID,也不是线程池中的线程。调用RunSynchronously方法时,会使用相同的线程,会创建一个任务。
3.使用单独线程的任务
上面将到的任务虽然不是线程池中的线程,但使用的是主线程,不是单独的,不能实现异步。
如果任务的代码应该长时间运行,就应该使用TaskCreationOptions.LongRunning告诉任务调度器创建一个新的单独线程,而不是线程池中的线程。这个线程可以不由线程池管理。当线程来自线程池时,任务调度器可以决定等待已经运行的任务完成,然后使用这个线程,而不是在线程池中创建一个新线程。对于长时间运行的线程,任务调度器会立即知道等待它们完成是不明智的做法,会创建一个新的线程。
示例:
static void LongRunTask()
{
var t = new Task(TaskMethod,"long running",TaskCreationOptions.LongRunning);
t.Start();
}
输出:
二.任务的结果————Future
任务结束时,可以把一些有用的状态信息写入共享对象中。这个共享对象必须是线程安全的。另一个选项是使用返回某个结果的任务。这种任务也叫future,因为它在将来返回一个结果。这需要使用Task类的一个泛型版本。使用这个类可以定义任务返回的结果的类型。
示例:
使用泛型类Task<TResult>,TResult是返回类型。通过构造函数,把方法传递给Func委托,第二个参数是委托的参数。
static void Main(string[] args)
{
var t = new Task<Tuple<int, int>>(TaskWithResult,Tuple.Create<int,int>(8,3));
t.Start();
Console.WriteLine(t.Result);
t.Wait();
Console.WriteLine("result from task:{0},{1}", t.Result.Item1, t.Result.Item2);
Console.ReadKey();
}
由任务来调用来返回结果的方法可以声明为任何类型。
static Tuple<int, int> TaskWithResult(object o)
{
Tuple<int, int> div = (Tuple<int, int>)o;
int result = div.Item1 / div.Item2;
int reminder = div.Item1 % div.Item2;
Thread.Sleep(10000);
return Tuple.Create<int, int>(result,reminder);
}
这里使用了元组(https://www.jb51.net/article/244045.htm).
三.连续的任务
通过任务,可以指定在任务完成后,应接着运行另一个特定任务。例如,一个使用前一个任务的结果的新任务,如果前一个任务失败了,这个任务就应执行一些清理工作。
任务处理程序(前一个任务)或者不带参数,或者带一个对象参数,而连续处理程序有一个Task类型的参数,这里可以访问前一个任务的相关信息。
示例:
//一个任务结束时,可以启动多个任务,连续任务也可以有另一个连续的任务。
static void Main(string[] args)
{
Task t1 = new Task(DoFirst);
Task t2 = t1.ContinueWith(DoSecond);
Task t3 = t1.ContinueWith(DoSecond);
Task t4= t2.ContinueWith(DoSecond);
t1.Start();
Console.ReadKey();
}
static void DoFirst()
{
Console.WriteLine("do some task:{0}",Task.CurrentId);
Thread.Sleep(3000);
}
static void DoSecond(Task t)
{
Console.WriteLine("task {0} finished",t.Id);
Console.WriteLine("this task id:{0}",Task.CurrentId);
}
无论前一个任务是如何结束,前面的连续任务总是在前一个任务结束时启动。使用TaskContinuationOptions枚举中的值,可以指定,连续任务只有在任务成功或失败时启动。
Task t5 = t1.ContinueWith(DoSecond,TaskContinuationOptions.OnlyOnFaulted);
四.任务的层次结构
利用任务连续性,可以在一个任务结束后启动另一个任务。任务也可以构成一个层次结构。在一个任务中启动一个新的任务时,就启动了一个父/子层次结构。取消父任务,也会取消子任务。
创建子任务与创建父任务的代码相同,唯一区别就就是子任务从另一个任务内部创建。
示例:
static void Main(string[] args)
{
Task t = new Task(ParentTask);
t.Start();
Console.ReadKey();
}
static void ParentTask()
{
Console.WriteLine("parent task id:{0}",Task.CurrentId);
var child = new Task(ChildTask);
child.Start();
Console.WriteLine("parent create child");
}
static void ChildTask()
{
Console.WriteLine("child task");
}
如果父任务在子任务之前结束,父任务的状态就是WaitingForChildrenToComplete。所有子任务也结束时,父任务的状态就是RanToCompletion.
来源:https://www.cnblogs.com/afei-24/p/6907840.html
猜你喜欢
- 给大家看个计算题,看看大家的算术能力。0.1 +0.1 +0.1 - 0.3 等于几?大家可能会说这么简单的问题,是不是看不起我?肯定等于0
- 写在前面很久以前就听nice0e3师傅说打Fastjson可以试试C3P0,当时还不会java(虽然现在也没会多少)也就没有深究。最近调试F
- 使用resilience4j的库和Spring Boot设计高弹性的微服务。微服务本质上是分布式的。当您使用分布式系统时,请始终记住这一第一
- 在C# 的应用程序开发中, 我们经常要把UI线程和工作线程分开,防止界面停止响应, 同时我们又需要在工作线程中更新UI界面上的控件。但直接访
- 前言本文主要给大家介绍了关于利用Spring Data MongoDB持久化文档数据的相关内容,分享出来供大家参考学习,下面话不多说了,来一
- 数据类型大小范围默认值byte(字节)8-128 - 1270shot(短整型)16-32768 - 327680int(整型)32-214
- 本文实例为大家分享了Android仿微信长按录制视频并播放功能的具体代码,供大家参考,具体内容如下一、点击按钮进行录制首先要获取摄像拍照的权
- 引言在项目中,时间的使用必不可少,而java 8之前的时间api Date和Calander等在使用上存在着很多问题,于是,jdk1.8引进
- 前言:WPF数据绑定对于WPF应用程序来说尤为重要,本文将讲述使用MVVM模式进行数据绑定的四步走用法:具体实例代码如下:public cl
- 本文实例为大家分享了Spring MVC接口防数据篡改和重复提交的具体代码,供大家参考,具体内容如下一、自定义一个注解,此注解可以使用在方法
- FileStream对象表示在磁盘或网络路径上指向文件的流。可以使用FileStream 类对文件系统上的文件进行读取、写入、打开、关闭等。
- mybatis在持久层框架中还是比较火的,一般项目都是基于ssm。虽然mybatis可以直接在xml中通过SQL语句操作数据库,很是灵活。但
- 一、引言在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,
- 本文实例为大家分享了android音乐播放器的具体代码,供大家参考,具体内容如下话不多说先上效果前言写这个音乐播放器实在是迫不得已。因为我们
- 一、前言ConcurrentHashMap的源码采用了一种比较独特的方式对map中的元素数量进行统计,自然是要好好研究一下其原理思想,同时也
- 1.介绍在SpringBoot的Web项目中,默认采用的是内置Tomcat,当然也可以配置支持内置的jetty,内置有什么好处呢? 1. 方
- 本文实例讲述了Android布局之LinearLayout自定义高亮背景的方法。分享给大家供大家参考,具体如下:首先创建linearlayo
- C#动态创建lambda表达式近日在使用了一下EF框架,在做多条件where查询的时候不知道怎么做,网上找了找,一开始用context.Da
- 在android移动端的开发中,首页轮播图是一个特别常见的功能,所以今天就来将最近写的一个小demo记录一下。首先当然是新建一个项目代码如下
- Java * 要想了解Java * ,首先要了解什么叫做代理,熟悉设计模式的朋友一定知道在Gof总结的23种设计模式中,有