【转】细说.NET中的多线程 (三 使用Task)

上一节我们介绍了线程池相关的概念以及用法。我们可以发现ThreadPool. QueueUserWorkItem是一种起了线程之后就不管了的做法。但是实际应用过程,我们往往会有更多的需求,比如如何更简单的知道线程池里面的某些线程什么时候结束,线程结束后如何执行别的任务。Task可以说是ThreadPool的升级版,在线程任务调度,并行编程中都有很大的作用。

创建并且初始化Task

使用lambda表达式创建Task

1

2

3

4

Task.Factory.StartNew(() => Console.WriteLine("Hello from a task!"));

 

var task = new Task(() => Console.Write("Hello"));

task.Start();

  

用默认参数的委托创建Task

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

using System;

using System.Threading.Tasks;

 

namespace MultiThread

{

    class ThreadTest

    {

        static void Main()

        {

            var task = Task.Factory.StartNew(state => Greet("Hello"), "Greeting");

            Console.WriteLine(task.AsyncState);   // Greeting

            task.Wait();

        }

 

        static void Greet(string message) { Console.Write(message); }

 

    }

}

  

这种方式的一个优点是,task.AsyncState作为一个内置的属性,可以在不同线程中获取参数的状态。

System.Threading.Tasks.TaskCreateOptions

创建Task的时候,我们可以指定创建Task的一些相关选项。在.Net 4.0中,有如下选项:

LongRunning

用来表示这个Task是长期运行的,这个参数更适合block线程。LongRunning线程一般回收的周期会比较长,因此CLR可能不会把它放到线程池中进行管理。

PreferFairness

表示让Task尽量以公平的方式运行,避免出现某些线程运行过快或者过慢的情况。

AttachedToParent

表示创建的Task是当前线程所在Task的子任务。这一个用途也很常见。

下面的代码是创建子任务的示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

using System;

using System.Threading;

using System.Threading.Tasks;

 

namespace MultiThread

{

    class ThreadTest

    {

        public static void Main(string[] args)

        {

            Task parent = Task.Factory.StartNew(() =>

            {

                Console.WriteLine("I am a parent");

 

                Task.Factory.StartNew(() =>        // Detached task

                {

                    Console.WriteLine("I am detached");

                });

 

                Task.Factory.StartNew(() =>        // Child task

                {

                    Console.WriteLine("I am a child");

                }, TaskCreationOptions.AttachedToParent);

            });

 

            parent.Wait();

 

            Console.ReadLine();

        }

 

    }

}

  

如果你等待你一个任务结束,你必须同时等待任务里面的子任务结束。这一点很重要,尤其是你在使用Continue的时候。(后面会介绍)

等待Task

在ThreadPool内置的方法中无法实现的等待,在Task中可以很简单的实现了:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

using System;

using System.Threading;

using System.Threading.Tasks;

 

namespace MultiThread

{

    class ThreadTest

    {

        static void Main()

        {

            var t1 = Task.Run(() => Go(null));

            var t2 = Task.Run(() => Go(123));

            Task.WaitAll(t1, t2);//等待所有Task结束

            //Task.WaitAny(t1, t2);//等待任意Task结束

        }

 

        static void Go(object data)   // data will be null with the first call.

        {

            Thread.Sleep(5000);

            Console.WriteLine("Hello from the thread pool! " + data);

        }

    }

}

  

注意:

当你调用一个Wait方法时,当前的线程会被阻塞,直到Task返回。但是如果Task还没有被执行,这个时候系统可能会用当前的线程来执行调用Task,而不是新建一个,这样就不需要重新创建一个线程,并且阻塞当前线程。这种做法节省了创建新线程的开销,也避免了一些线程的切换。但是也有缺点,当前线程如果和被调用的Task同时想要获得一个lock,就会导致死锁。

Task异常处理

当等待一个Task完成的时候(调用Wait或者或者访问Result属性的时候),Task任务中没有处理的异常会被封装成AggregateException重新抛出,InnerExceptions属性封装了各个Task没有处理的异常。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

using System;

using System.Threading.Tasks;

 

namespace MultiThreadTest

{

    class Program

    {

        static void Main(string[] args)

        {

            int x = 0;

            Task<int> calc = Task.Factory.StartNew(() => 7 / x);

            try

            {

                Console.WriteLine(calc.Result);

            }

            catch (AggregateException aex)

            {

                Console.Write(aex.InnerException.Message);  // Attempted to divide by 0

            }

        }

    }

}

  

对于有父子关系的Task,子任务未处理的异常会逐层传递到父Task,并且最后包装在AggregateException中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

using System;

using System.Threading.Tasks;

 

namespace MultiThreadTest

{

    class Program

    {

        static void Main(string[] args)

        {

            TaskCreationOptions atp = TaskCreationOptions.AttachedToParent;

            var parent = Task.Factory.StartNew(() =>

            {

                Task.Factory.StartNew(() =>   // Child

                {

                    Task.Factory.StartNew(() => { throw null; }, atp);   // Grandchild

                }, atp);

            });

 

            // The following call throws a NullReferenceException (wrapped

            // in nested AggregateExceptions):

            parent.Wait();

        }

    }

}

  

取消Task

如果想要支持取消任务,那么在创建Task的时候,需要传入一个CancellationTokenSouce

示例代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

using System;

using System.Threading;

using System.Threading.Tasks;

 

namespace MultiThreadTest

{

    class Program

    {

        static void Main(string[] args)

        {

            var cancelSource = new CancellationTokenSource();

            CancellationToken token = cancelSource.Token;

 

            Task task = Task.Factory.StartNew(() =>

            {

                // Do some stuff...

                token.ThrowIfCancellationRequested();  // Check for cancellation request

                // Do some stuff...

            }, token);

            cancelSource.Cancel();

 

            try

            {

                task.Wait();

            }

            catch (AggregateException ex)

            {

                if (ex.InnerException is OperationCanceledException)

                    Console.Write("Task canceled!");

            }

 

            Console.ReadLine();

        }

    }

}

  

任务的连续执行

Continuations

任务调度也是常见的需求,Task支持一个任务结束之后执行另一个任务。

1

2

Task task1 = Task.Factory.StartNew(() => Console.Write("antecedant.."));

Task task2 = task1.ContinueWith(task =>Console.Write("..continuation"));

  

Continuations 和Task<TResult>

Task也有带返回值的重载,示例代码如下:

1

2

3

4

Task.Factory.StartNew<int>(() => 8)

    .ContinueWith(ant => ant.Result * 2)

    .ContinueWith(ant => Math.Sqrt(ant.Result))

    .ContinueWith(ant => Console.WriteLine(ant.Result));   // output 4

  

子任务

前面提到了,当你等待一个任务的时候,同时需要等待它的子任务完成。

下面代码演示了带子任务的Task:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

using System;

using System.Threading.Tasks;

using System.Threading;

 

namespace MultiThreadTest

{

    class Program

    {

        public static void Main(string[] args)

        {

            Task<int[]> parentTask = Task.Factory.StartNew(() =>

            {

                int[] results = new int[3];

 

                Task t1 = new Task(() => { Thread.Sleep(3000); results[0] = 0; }, TaskCreationOptions.AttachedToParent);

                Task t2 = new Task(() => { Thread.Sleep(3000); results[1] = 1; }, TaskCreationOptions.AttachedToParent);

                Task t3 = new Task(() => { Thread.Sleep(3000); results[2] = 2; }, TaskCreationOptions.AttachedToParent);

 

                t1.Start();

                t2.Start();

                t3.Start();

 

                return results;

            });

 

            Task finalTask = parentTask.ContinueWith(parent =>

            {

                foreach (int result in parent.Result)

                {

                    Console.WriteLine(result);

                }

            });

 

            finalTask.Wait();

            Console.ReadLine();

        }

    }

}

  

这段代码的输出结果是: 1,2,3

FinalTask会等待所有子Task结束后再执行。

TaskFactory

关于TaskFactory,上面的例子中我们使用了System.Threading.Tasks .Task.Factory属性来快速的创建Task。当然你也可以自己创建TaskFactory,你可以指定自己的TaskCreationOptions,TaskContinuationOptions来使得通过你的Factory创建的Task默认行为不同。

.Net中有一些默认的创建Task的方式,由于TaskFactory创建Task的默认行为不同可能会导致一些不容易发现的问题。

如在.NET 4.5中,Task加入了一个Run的静态方法:

Task.Run(someAction);

如果你用这个方法代替上面例子中的Task.Factory.StartNew,就无法得到正确的结果。原因是Task.Run创建Task的行为默认是默认是拒绝添加子任务的。上面的代码等价于:

    Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

你也可以创建具有自己默认行为的TaskFactory。

 

无论ThreadPool也好,或者Task,微软都是在想进办法来实现线程的重用,来节省不停的创建销毁线程带来的开销。线程池内部的实现可能在不同版本中有不同的机制。如果可能的话,使用线程池来管理线程仍然是建议的选择。

 

我们主要介绍了一下Task的基本用法,在我们编程过程中,有一些使用Task来提升程序性能的场景往往是很相似的,微软为了简化编程,在System.Threading.Tasks.Parallel中封装了一系列的并行类,内部也是通过Task来实现的。

Parallel的For,Foreach,Invoke 方法

在编程过程中,我们经常会用到循环语句:

 

1

2

3

4

for (int i = 0; i < 10; i++)

{

    DoSomeWork(i);

}

  

如果循环过程中的工作可以是并行的话,那么我们可以用如下语句:

 

1

Parallel.For(0, 10, i => DoSomeWork(i));

  

我们也经常会使用Foreach来遍历某个集合:

 

1

2

3

4

foreach (var item in collection)

{

    DoSomeWork(item);

}

  

如果我们用一个线程池来执行里面的任务,那么我们可以写成:

 

1

Parallel.ForEach(collection, item => DoSomeWork(item));

  

最后,如果你想并行的执行几个不同的方法,你可以:

 

1

Parallel.Invoke(Method1, Method2, Method3);

  

如果你看下后台的实现,你会发现基本都是基于Task的线程池,当然你也可以通过手动创建一个Task集合,然后等待所有的任务结束来实现同样的功能。上面的Parallel.For和Parallel.Forach方法并不意味着你可以寻找你代码里面所有用到For和Foreach方法,并且替代他们,因为每一个任务都会分配一个委托,并且在线程池里执行,如果委托里面的任务是线程不安全的,你可能还需要lock来保证线程安全,使用lock本身就会造成性能上的损耗。如果每一个任务都是需要长时间执行并且线程安全的,Parallel会给你带来不错的性能提升。对于短任务,或者线程不安全的任务,你需要权衡下,你是否真的需要使用Parallel


作者:独上高楼
出处:http://www.cnblogs.com/myprogram/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/436756.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java volidate_volidate 学习

一&#xff1a;Volatile 变量具有synchronized的可见性&#xff0c;有序性 特性&#xff0c;但是不具备原子特性二&#xff1a;java memory model(jmm) java 内存模型形象理解见下图Java Memory Modela&#xff1a;java 线程读取共享内存变量流程&#xff1a;线程2 --> JMM …

【转】ABP源码分析一:整体项目结构及目录

ABP是一套非常优秀的web应用程序架构&#xff0c;适合用来搭建集中式架构的web应用程序。 整个Abp的Infrastructure是以Abp这个package为核心模块(core)15个模块(module).其中13个依赖于Abp这个核心包。另外两个包&#xff08;FluentMigration,Web.Resources&#xff09;相对独…

JavaScript 中的短路求值(if语句简洁写法--逻辑运算符||和的高级用法)

在JavaScript中&#xff0c;Short-Circuit Evaluation&#xff08;短路求值&#xff09;是一种逻辑运算的行为&#xff0c;其中表达式的求值在达到不必要的部分时就提前终止&#xff08;所以短路一词非常贴切&#xff09;。这种行为可以通过逻辑运算符&#xff08;例如&&am…

【转】ABP源码分析二:ABP中配置的注册和初始化

一般来说&#xff0c;ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法。执行这个方法前HttpApplication 实例必须存在&#xff0c;也就是说其构造函数必然已完成了执行。 ABP开始的地方就是HttpApplication的构造函数。 如下图一&#xff0c;Abp定义了一…

python文本进度条94页_Python学习笔记 | 实例4:文本进度条

本文为中国大学MOOC《Python语言程序设计》课程学习笔记&#xff0c;课程主讲&#xff1a;嵩天老师&#xff0c;练习平台&#xff1a;Python123&#xff0c;参考教材&#xff1a;《Python语言程序设计基础》文本进度条-简单的开始import timescale 10print("----执行开始…

【转】ABP源码分析三:ABP Module

Abp是基于模块化设计思想进行构建的。开发人员可以将自定义的功能以模块&#xff08;module&#xff09;的形式集成到ABP中。具体的功能都可以设计成一个单独的Module。Abp底层框架提供便捷的方法集成每个Module.下图是所有Abp自带的module.AbpModule是所有Module的基类&#x…

java weka 聚类_简单开源数据挖掘工具weka进行文本聚类

目前非代码的数据挖掘工具很多&#xff0c;但非开源&#xff0c;weka是一款开源软件。只要安装jdk环境就可使用(具体安装jdk可以百度)本文将论述如何不用代码&#xff0c;使用weka操作&#xff0c;通过与文档频数与单词权的特征选择方法进行文本聚类(数据为附件)第一步&#xf…

java 不识别enum_Java enum关键字不识别的快速解决办法

从别人那儿拷贝过来的myeclipse java工程&#xff0c;打开一看标红了一大片&#xff0c;仔细一看&#xff0c;原来是不识别enum关键字&#xff0c;这就有点尴尬了。我自己重新建了一个java工程&#xff0c;测试了下&#xff0c;假如我在新建工程的时候选择javase-1.6&#xff1…

【转】ABP源码分析四:Configuration

核心模块的配置 Configuration是ABP中设计比较巧妙的地方。其通过AbpStartupConfiguration&#xff0c;Castle的依赖注入&#xff0c;Dictionary对象和扩展方法很巧妙的实现了配置中心化。配置中心化是一个支持模块开发的框架必备功能。 ABP中核心功能模块中的一些功能的运行时…

java 实现 堆和栈_JAVA中的堆和栈

JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说&#xff0c;它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。差异&#xff1a;1.堆内存用来存放由new…

【转】ABP源码分析五:ABP初始化全过程

ABP在初始化阶段做了哪些操作&#xff0c;前面的四篇文章大致描述了一下。 为个更清楚的描述其脉络&#xff0c;做了张流程图以辅助说明。其中每一步都涉及很多细节&#xff0c;难以在一张图中全部表现出来。每一步的细节&#xff08;会涉及到较多接口&#xff0c;类&#xff0…

【转】ABP源码分析六:依赖注入的实现

ABP的依赖注入的实现有一个本质两个途径&#xff1a;1.本质上是依赖于Castle这个老牌依赖注入的框架。2.一种实现途径是通过实现IConventionalDependencyRegistrar的实例定义注入的约定&#xff08;规则&#xff09;&#xff0c;然后通过IocManager来读取这个规则完成依赖注入。…

java从小到大排序函数_利用随机函数产生10个1~100之间的整数,按从小到大的顺序排列输出...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼public interface planeGraphics2{public abstract double area();public abstract double perimeter();public abstract void print();}public class rectangle2 implements planeGraphics2{protected double length;protected do…

【转】ABP源码分析七:Setting 以及 Mail

本文主要说明Setting的实现以及Mail这个功能模块如何使用Setting. 首先区分一下ABP中的Setting和Configuration。 Setting一般用于需要通过外部配置文件&#xff08;或数据库&#xff09;设置的简单类型数据&#xff08;一般就是字符串&#xff09;&#xff0c;比如SMTP HOST.…

java流换行符方法_java换行符的使用方法

java换行符的使用方法发布时间&#xff1a;2020-06-22 17:49:56来源&#xff1a;亿速云阅读&#xff1a;121作者&#xff1a;Leah这篇文章将为大家详细讲解有关java换行符的使用方法&#xff0c;小编觉得挺实用的&#xff0c;因此分享给大家做个参考&#xff0c;希望大家阅读完…

【转】ABP源码分析八:Logger集成

ABP使用Castle日志记录工具&#xff0c;并且可以使用不同的日志类库&#xff0c;比如&#xff1a;Log4Net, NLog, Serilog... 等等。对于所有的日志类库&#xff0c;Castle提供了一个通用的接口来实现&#xff0c;我们可以很方便的处理各种特殊的日志库&#xff0c;而且当业务需…

java并发锁获取的方式_Java精通并发-notify方法详解及线程获取锁的方式分析

wait()&#xff1a;在上一次https://www.cnblogs.com/webor2006/p/11404521.html中对于无参数的wait()方法的javadoc进行了解读&#xff0c;而它是调用了一个参数的重载方法&#xff0c;回忆下&#xff1a;其中如果传0代表无限等待&#xff0c;否则是等待指定的时间就会停止等待…

【转】ABP源码分析九:后台工作任务

文主要说明ABP中后台工作者模块&#xff08;BackgroundWorker&#xff09;的实现方式&#xff0c;和后台工作模块&#xff08;BackgroundJob&#xff09;。ABP通过BackgroundWorkerManager来管理BackgroundJobManager&#xff0c;然后通过BackgroundJobManager来管理Background…

java引入resource下的模板_Beetl自定义ResourceLoader,实现特殊的模板加载需求

一直以来&#xff0c;有个目标是&#xff1a;使用Beetl的时候&#xff0c;如果web root 里有模板文件&#xff0c;则beetl从web root里加载。如果没有&#xff0c;则从jar里加载&#xff0c;或者从Db里加载。这样&#xff0c;工程里大量相同的模板模板可以共用(当你的应用&…

【转】ABP源码分析十:Unit Of Work

ABP以AOP的方式实现UnitOfWork功能。通过UnitOfWorkRegistrar将UnitOfWorkInterceptor在某个类被注册到IOCContainner的时候&#xff0c;一并添加到该类在容器中对应的ComponentModel的Interceptors集合中。总结一句话就是&#xff0c;UOW的功能是通过自定义Castle拦截器来实现…