前言
上一篇我们重要先容了并行编程相干的知识,这一节我们继承先容关于任务相干的知识。为了更好的控制并行操纵,我们可以使用System.Threading.Tasks中的Task类。我们起首来相识是什么是任务——任务表现将要完成的一个或某个工作单元,这个工作单元可以在单独线程中运行,也可以使用同步方式启动运行(须要期待主线程调用)。为什么使用任务呢?——任务不但可以得到一个抽象层(将要完成的工作单元)、还可以对底层的线程运行举行更好更多的控制(任务的运行)。
使用线程池的任务
我们讲到使用任务可以更好更多的控制底层的线程。就涉及到——线程池,线程池提供的是一个配景线程的池。线程池独自管理线程、根据需求增长或镌汰线程数。使用完成的线程返回至线程池中。我们下面就看看创建任务:
我们看下创建任务的几种方式:
1、使用实例化的TaskFactory类,然后使用其StartNew方法启动任务。
2、使用Task静态的Factory以来访问TaskFactory,然后调用StartNew方法启动任务。与第一种相似,但是对工厂的创建的控制就没那么全面。
3、使用Task的构造函数,实例化Task对象来指定创建任务,然后通过Start()方法举行启动任务。
4、使用Task.Run方法来立刻启动任务。
我们看下以上方法创建的任务有何区别和雷同吧,看代码:
- private static object _lock = new object();<br /> public static void TaskMethond(object item)
- {
- lock (_lock)
- {
- Console.WriteLine(item?.ToString());
- Console.WriteLine($"任务Id:{Task.CurrentId?.ToString() ?? "没有任务运行"}\t 线程Id:{Thread.CurrentThread.ManagedThreadId}");
- Console.WriteLine($"是否是线程池中的线程:{Thread.CurrentThread.IsThreadPoolThread}");
- Console.WriteLine($"是否是配景线程:{Thread.CurrentThread.IsBackground}");
- Console.WriteLine();
- }
- }
- #region 任务创建
- public static void TaskCreateRun()
- {
- var taskFactory = new TaskFactory();
- var task1 = taskFactory.StartNew(TaskMethond, "使用实例化TaskFactory");
- var task2 = Task.Factory.StartNew(TaskMethond, "使用Task静态调用Factory");
- var task3 = new Task(TaskMethond, "使用Task构造函数实例化");
- task3.Start();
- var task4 = Task.Run(() => TaskMethond("使用Task.Run"));
- }
- #endregion
复制代码
我们看代码运行的效果,发现不管使用的那种方法创建任务,都是使用过的线程池中的线程。
使用单独线程的任务
任务固然也不愿定就是使用线程池中的线程运行的,也是可以使用其他线程的。如果任务的将长时间运行的话,我们尽大概的考虑使用单独线程运行(TaskCreationOptions.LongRunning),这个环境下线程就不由线程池管理。我们看下倒霉用线程池中线程运行,使用单独线程运行的任务。
- #region 单独线程运行任务
- public static void OnlyThreadRun()
- {
- var task1 = new Task(TaskMethond, "单独线程运行任务", TaskCreationOptions.LongRunning);
- task1.Start();
- }
- #endregion
复制代码
我们看其运行效果,仍旧是配景线程,但是不是使用的线程池中的线程。
使用同步任务
同时任务也可以同步运行,以雷同的线程作为调用线程,下面我们看下使用Task类中的RunSynchronoushly方法实现同步运行。
- #region 同步任务
- public static void TaskRunSynchronoushly()
- {
- TaskMethond("主线程调用");
- var task1 = new Task(TaskMethond, "任务同步调用");
- task1.RunSynchronously();
- }
- #endregion
复制代码
我们看运行效果,发现起首调用TaskMethond方法时间没有任务而且使用的线程1,再我们创建Task实例运行TaskMethond方法的时间,任务id是1,但是线程我们依然使用的是主线程1。
一蝉联务
在任务中,我们可以指定在某个任务完成后,应该立刻开始别的一个任务。比如一个任务完成之后应该继承其处置惩罚。但是失败后我们应该举行一些处置惩罚工作。
我们可以使用ContinueWith()方法来定义使用一蝉联务,表现某任务之后应该开始其他任务,我们也可以指定任务乐成后开始某个任务大概失败后开启某个任务(TaskContinuationOptions)。
- #region 一蝉联务
- public static void TaskOne()
- {
- Console.WriteLine($"任务{Task.CurrentId},方法名:{System.Reflection.MethodBase.GetCurrentMethod().Name }启动");
- Task.Delay(1000).Wait();
- Console.WriteLine($"任务{Task.CurrentId},方法名:{System.Reflection.MethodBase.GetCurrentMethod().Name }竣事");
- }
- public static void TaskTwo(Task task)
- {
- Console.WriteLine($"任务{task.Id}以及竣事了");
- Console.WriteLine($"如今开始的 任务是任务{Task.CurrentId},方法名称:{System.Reflection.MethodBase.GetCurrentMethod().Name } ");
- Console.WriteLine($"任务处置惩罚");
- Task.Delay(1000).Wait();
- }
- public static void TaskThree(Task task)
- {
- Console.WriteLine($"任务{task.Id}以及竣事了");
- Console.WriteLine($"如今开始的 任务是任务{Task.CurrentId}.方法名称:{System.Reflection.MethodBase.GetCurrentMethod().Name } ");
- Console.WriteLine($"任务处置惩罚");
- Task.Delay(1000).Wait();
- }
- public static void ContinueTask()
- {
- Task task1 = new Task(TaskOne);
- Task task2 = task1.ContinueWith(TaskTwo, TaskContinuationOptions.OnlyOnRanToCompletion);//已完成环境下继承任务
- Task task3 = task1.ContinueWith(TaskThree, TaskContinuationOptions.OnlyOnFaulted);//出现未处置惩罚非常环境下继承任务
- task1.Start();
- }
- #endregion
复制代码
我们看代码中写的是先开始运行TaskOne(),然后当任务完成后运行TaskTwo(Task task) ,如果任务失败的话时机运行TaskThree(Task task)。我们看运行效果中是运行了TaskOne()然后乐成后运行了TaskTwo(Task task),避开了TaskThree(Task task)的运行,所以我们是可以通过ContinueWith来举行一蝉联务和TaskContinuationOptions举行控制任务运行的。
任务条理—父子条理结构
这里我们使用任务的一连性,我就就可以实如今一个任务竣过后立刻开启另一个任务,任务也可以构成一个条理结构。就比如一个任务中启动了一个任务,如许的环境就形成烈?子条理的结构。下面我们看的案例就是这么一个案例。
- #region 任务的条理结构——父子条理结构
- public static void ChildTask()
- {
- Console.WriteLine("当前运行的子任务,开启");
- Task.Delay(5000).Wait();
- Console.WriteLine("子任务运行竣事");
- }
- public static void ParentTask()
- {
- Console.WriteLine("父级任务开启");
- var child = new Task(ChildTask);
- child.Start();
- Task.Delay(1000).Wait();
- Console.WriteLine("父级任务竣事");
- }
- public static void ParentAndChildTask()
- {
- var parent = new Task(ParentTask);
- parent.Start();
- Task.Delay(2000).Wait();
- Console.WriteLine($"父级任务的状态 :{parent.Status}");
- Task.Delay(4000).Wait();
- Console.WriteLine($"父级任务的状态 :{parent.Status}");
- }
- #endregion
复制代码
期待任务
在前面问先容的.Net异步编程中我们讲到了WhenAll,用于处置惩罚多个异步方法。在这里我们继承扩展点,WhenAll()和WaitAll(),都是期待转达给他们的任务完成。但是WaitAll()方法壅闭调用任务,知道全部任务完成为止,而WhenAll()返回了一个任务,从而可以使用async关键在期待效果。不会壅闭任务。与之相对应的也还有WaitAny()和WhenAn()。期待任务还有我们不绝都用到了的Task.Delay()方法,指定这个方法放回的任务前要期待的毫秒数。
下面我们看这个ValueTask期待范例(结构),相对于Task类来说,ValueTask没有堆中对象的开销。在一样平常环境下,Task范例的开销可以被忽略掉,但是在一些特殊环境下,比方方法被调用千次万次来看。这种环境ValueTask就变得很实用了。我们看下面这个案例,使用ValueTask时,在五秒内的环境下直接从它的构造函数返回值。如果时间不在五秒内的话就使用真正获取数据的方法。然后我们与使用Task的方法举行对比。这里我们接纳十万条数据的测试对比。
- #region 期待任务
- private static DateTime _time;
- private static List<string> _data;
- public static async ValueTask<List<string>> GetStringDicAsync()
- {
- if (_time >= DateTime.Now.AddSeconds(-5))
- {
- return await new ValueTask<List<string>>(_data);
- }
- else
- {
- (_data, _time) = await GetData();
- return _data;
- }
- }
- public static Task<(List<string> data, DateTime time)> GetData() =>
- Task.FromResult(
- (Enumerable.Range(0, 10).Select(x => $"itemString{x}").ToList(), DateTime.Now));
- public static async Task<List<string>> GetStringList()
- {
- (_data, _time) = await GetData();
- return _data;
- }
- #endregion
复制代码
- static async Task Main(string[] args)
- {
- Stopwatch stopwatch = new Stopwatch();
- stopwatch.Start();
- Console.WriteLine("ValueTask开始");
- for (int i = 0; i < 100000; i++)
- {
- var itemList= await GetStringDicAsync();
- }
- Console.WriteLine("ValueTask竣事");
- Console.WriteLine($"ValueTask耗时:{stopwatch.ElapsedMilliseconds}");
- Console.WriteLine();
- Console.WriteLine();
- stopwatch.Restart();
- Console.WriteLine("Task开始");
- for (int i = 0; i < 100000; i++)
- {
- var itemList = await GetStringList();
- }
- Console.WriteLine("Task竣事");
- Console.WriteLine($"Task耗时:{stopwatch.ElapsedMilliseconds}");
- Console.ReadLine();
- }
复制代码
我们看其运行效果,使用Task和ValueTask的运行效果耗时相差是巨大的。所以在一些特殊环境下使用ValueTask大概会更加的实用。
总结
本日我们先容了关于任务相干的一些知识概念。我们连合上一篇文章我们来梳理一些任务、线程、多线程、异步、同步、并发、并行任务之间的接洽与关系吧。
起首我们看我们这章节学习的任务、任务是一个将要完成的工作单元,那么由谁完成呢?由线程来运行这个任务。那么关于多线程呢?多线程应该可以说是一个筹划概念,用来实现线程切换的。多线程就可以运行多个任务,但是在并发中。在同一时间内只能有一个步调运行。只不过线程间切换速率极快,让它看起来似乎是在同一时间运行了多个步调。其着实微观上讲,并发在恣意时间点只有一个步调在运行,只不过是线程切换速率快。那么怎么到达切换速率极快呢?这就须要异步了。在线程运行的时间不须要比及它完成效果再去继承其他的线程任务。也就是可期待的。实现A运行起来不期待其效果,然后切换到B继承运行。如许切换速率就极快了。我们再仔细看多线程切换线程似乎成了实现异步的一种方法手段了。有异步就有同步,同步来说就不须要使用到多线程了,没须要。反正比及上一个任务运行完成。就继承使用上一个线程继承运行。这里都是讲的并发中的环境。那么并行呢?并行可以说不管在微观照旧宏观上都是可以实现一个时间运行多个步调的。并发是多个步调运行在一个处置惩罚机上,但是并行任务是运行在多个处置惩罚机上。比方实现四个任务并行,那么我们至少须要四个逻辑处置惩罚内核的共同才气到达。
项目源码所在
天下上那些最容易的事变中,耽搁时间最不费力。坚固是乐成的一大要素,只要在门上敲得够久够高声,终会把人叫醒的。
欢迎各人扫描下方二维码,和我一起学习更多的知识
|