C#之:并行编程 -4

Task (任务):

为了更好地控制并行动作,可以使用 System.Threading.Task 命名空间下的 Task 类,任务表示完成的某个工作单元。这个工作单元可以在单独的线程中运行,也可以以同步的方式启动一个任务,这需要等待主调线程。任务不经可以获得一个抽象层,还可以对层线程进行很多控制。

官方链接:Task

1. 启动任务:

要启动任务,可以使用 TaskFactory类或 Task 类的构造函数和 Start() 方法。Task类的构造函数在创建任务上提供的灵活性较大。

在启动任务时,会创建 Task 类的一个 实例,利用 Action 或 Action< object > 委托(无参或带一个object 参数),可以指定运行的代码。如下:

     static object locker = new object();
      static void TaskMethod(object obj)
        {
            lock (locker)
            {
                Console.WriteLine(obj);
                Console.WriteLine("任务ID:{0}, 线程ID:{1}",Task.CurrentId==null?"no task":Task.CurrentId.ToString(),Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine("是池线程?: {0}",Thread.CurrentThread.IsThreadPoolThread);
                Console.WriteLine("是后台线程?: {0}",Thread.CurrentThread.IsBackground);
                Console.WriteLine();
            }
        }

在上面方法中,打印出任务ID和线程ID,并且如果来自一个线程池,或线程是一个后台线程也要打印到控制台。把多条信息写入控制台的操作是使用 Lock 关键字和 locker 同步对象进行同步的。这样就可以并行调用 TaskMethod 方法。而多次写入控制台的操作也不会彼此交叉。否则,obj 可能有一个任务写入,而线程信息有另一个任务写入。

启动任务的几个不同方法:

1:使用线程池启动任务

        static void TaskUsingThreadPool()
        {
            var tf = new TaskFactory();
            Task t1 = tf.StartNew(TaskMethod, "使用任务工厂启动任务");
            Task t2 = Task.Factory.StartNew(TaskMethod, "通过工厂启动任务");
            Task t3 = new Task(TaskMethod, "使用任务构造函数并启动");
            t3.Start();
            Task t4 = Task.Run(() => TaskMethod("使用 Run 方法"));
        }

输出:
在这里插入图片描述
说明:
第一中方法是 实例化 TaskFactory 类,在其中把 TaskMethod 方法传递给 StartNew方法,就会立即启动任务。
第二中方法是 使用 Task 类的静态属性 Factory 来访问 TaskFactory,以及调用 StartNew() 方法。
第三中方式是使用 Task 类的构造函数。实例化 Task 对象时,任务不会立即运行,而是指定 Created 状态。接着调用 Task 类的 Start() 方法,来启动任务。
第四中方式是 .NET4.5 新增的,调用 Task 的 Run() 方法,立即启动任务。Run 方法没有可以传递 Action< object > 委托的重载版本,但是通过传递 Action 类型 的 lambda 表达式并在其实现中使用参数,可以模拟这种行为。

2:同步任务
任务不一定使用线程池中的线程,也可以使用其他线程。任务也可以同步运行,以相同的线程作为主掉线程。 如下:

 static void RunSynchronousTask()
        {
            TaskMethod("使用主线程");
            var t1 = new Task(TaskMethod, "同步运行");
            t1.RunSynchronously();
        }

输出:
在这里插入图片描述
说明:
首先在主线程上直接调用,然后再新建的 Task 上调用。主线程是一个前台线程,没有任务ID,也不是线程池中的线程。调用 RunSynchronously() 方法时,会使用相同的线程作为主调线程,但是如果以前没有创建任务就会创建一个新任务。

3:使用单独的线程任务

如果任务代码需要长时间运行,就应该使用 TaskCreationOptions.LongRunning 告诉任务调度器,创建一个新线程,而不是使用线程池中的线程。此时,线程可以不由线程池管理。当线程来自线程池时,任务调度器可以决定等待已经运行的任务完成,然后使用这个线程,而不是再线程池中创建一个新线程。对于长时间运行的线程,任务调度器会立即知道等待他们完成不是明智的做法。如下代码,创建了一个长时间运行的任务:

 static void LongRunningTask()
        {
            var t1 = new Task(TaskMethod, "长时间运行", TaskCreationOptions.LongRunning);
            t1.Start();
        }

输出:
在这里插入图片描述
可以看出,使用 TaskCreationOptions.LongRunning 不会使用线程池,而是创建新线程。

4. 连续的任务:

通过任务可以指定在任务完成后,应该开始运行另一个任务,例如,一个使用前一个任务的结果的新任务,如果前一个任务失败了,这个任务就应该执行一些清理工作。
任务处理程序或者不带参数,或者带一个对象参数,而连续处理程序有一个 Task 类型的参数,这里可以访问起始任务的相关信息。

 static void Main(string[] args)
        {
            Task t1 = new Task(DoOnFirst);
            Task t2 = t1.ContinueWith(DoOnSecond);
            Task t3 = t1.ContinueWith(DoOnSecond);
            Task t4 = t3.ContinueWith(DoOnSecond);
            t1.Start();
            Console.ReadKey();
        }

        static void DoOnFirst()
        {
            Console.WriteLine("做一些任务,任务ID={0}",Task.CurrentId);
            Thread.Sleep(3000);
        }

        static void DoOnSecond(Task t)
        {
            Console.WriteLine("任务 ID={0} 完成",t.Id);
            Console.WriteLine("这个任务ID={0}",Task.CurrentId);
            Console.WriteLine("做一些清理工作...");
            Thread.Sleep(3000);
        }

输出:
在这里插入图片描述

说明:
连续任务通过在任务上调用 ContinueWith() 方法来定义。 也可以使用 TaskFactory 类来定义。 t1.ContinueWith(DoOnSecond)方法表示,调用DoOnSecond() 方法的新任务应该在任务 t1 结束时立即启动。在一个任务结束时,可以启动多个任务,连续任务也可以有另一个连续任务。
无论前一个任务时如何结束的,后面的连续任务总是在前一个任务结束时启动。使用 TaskContinuationOptions 枚举中的值,可以指定,连续任务只有在起始任务成功(或失败)结束时启动。一些可能的值是 :

  • NotOnFaulted: 指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。 此选项对多任务延续无效。
  • OnlyOnFaulted: 指定只应在延续任务前面的任务引发了未处理异常的情况下才安排延续任务。 此选项对多任务延续无效。
  • OnlyOnCanceled: 指定只应在延续任务前面的任务已取消的情况下才安排延续任务。 此选项对多任务延续无效。
  • NotOnCanceled: 指定不应在延续任务前面的任务已取消的情况下安排延续任务。 此选项对多任务延续无效。
  • NotOnRanToCompletion: 指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。 此选项对多任务延续无效。
  • OnlyOnRanToCompletion: 指定只应在延续任务前面的任务已完成运行的情况下才安排延续任务。 此选项对多任务延续无效。

等等。

例:

 Task t5 = t1.ContinueWith(DoOnError, TaskContinuationOptions.OnlyOnFaulted);

Future - 任务结果:

任务结束时,可以把一些有用的状态信息写到共享对象中。这个共享对象必须是线程安全的。
另一个选项是使用返回某个结果的任务。这种任务也叫 Future(未来),因为它再将来返回一个结果 。 早期版本的 Task Parallel Library ( TPL ) 的类名也叫 Future ,现在它是 Task 类的一个泛型版本。使用这个类时,可以定义任务返回的结果的类型。
由任务调用来返回结果的方法可以声明为任何返回类型。如下实例方法:

 static void Main(string[] args)
        {
            //LongRunningTask();
            var data = SampleData();
            var t1 =new  Task<double>(TaskWithRes, data);
            t1.Start();
            Console.WriteLine(t1.Result);
            t1.Wait();
            Console.WriteLine("任务返回结果: {0}",t1.Result);
            Console.ReadKey();
        }

        static double  TaskWithRes(object obj)
        {
            List<int> data = (List<int>) obj;

            //求 集合中 数字的自然对数(底为 e)的值小于4 的元素 的 平均值
           
           
            var res = (from x in data.AsParallel()
                       where Math.Log(x) < 4
                       select x
                     ).Average();
            return res;
        }
        static IEnumerable<int> SampleData()
        {
            const int arraySize = 90000000;
            var r = new Random();
            return Enumerable.Range(0, arraySize).Select(x => r.Next(500)).ToList();
        }

输出:
在这里插入图片描述

任务的层次结构:

利用任务的连续性,可以在一个任务结束后启动另一个任务。任务也可以构成一个层次结构。一个任务启动一个新任务时,就启动了一个父/子层次结构。
如下代码:在父任务内部新建一个任务对象并启动。创建子任务的代码与创建父任务的代码相同,唯一的区别时这个任务是从另一个任务内部启动。

static void Main(string[] args)
        {
            ParentAndChild();
            Console.ReadKey();
        }

        /// <summary>
        /// 子任务
        /// </summary>
        static void ChildTask()
        {
            Console.WriteLine("子任务");
            Thread.Sleep(3000);
            Console.WriteLine("子任务完成");
        }

        /// <summary>
        /// 父任务
        /// </summary>
        static void ParentTask()
        {
            Console.WriteLine("当前任务 ID={0}",Task.CurrentId);
            var child = new Task(ChildTask);
            child.Start();
            Thread.Sleep(1000);
            Console.WriteLine("父任务中启动子任务");
        }

        /// <summary>
        /// 调用任务
        /// </summary>
        static void ParentAndChild()
        {
            var parent = new Task(ParentTask);
            parent.Start();
            Thread.Sleep(1000);
            Console.WriteLine("父任务状态:{0}",parent.Status);
            Thread.Sleep(4000);
            Console.WriteLine("父任务状态:{0}", parent.Status);
        }

输出:
在这里插入图片描述
如果父任务在子任务之前结束,父任务的状态就会显示为 WaitingForChildrenToComplete 。所有的子任务也结束时,父任务的状态就变成 RunToCompletion。当然,如果父任务用 TaskCreationOptions 枚举中的 DetachedFromParent创建子任务时,这就无效了。

取消父任务,也会取消子任务。下一章重点讨论取消架构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值