C#之:并行编程 -2

Parallel.Invoke():并行调用

重载:

  1. Invoke(Action[]) : 尽可能并行执行提供的每个操作。
  2. Invoke(ParallelOptions, Action[]) :执行所提供的每个操作,而且尽可能并行运行,除非用户取消了操作。

需要并行调用一批方法,并且这些方法(大部分)是互相独立的。
如果多个任务并行运行,就可以使用 Parallel.Invoke() 方法。他提供了任务并行的模式。Parallel.Invoke()允许传递一个 Action 委托数组,在其中可以指定运行方法。

Dome1:

 static void Main(string[] args)
        {
            Parallel.Invoke(F1, F2);

            Console.ReadKey();
        }

        static void F1()
        {
            Console.WriteLine("方法1");
        }

        static void F2()
        {
            Console.WriteLine("方法2");
        }

简单的 方法直接写 lambda 表达式更好。

Dome2: 实例

static void Main(string[] args)
        {
            double[] arr = { 12.34, 234.12, 5.445, 1200.002, 349.22, 736.294, 482, 48.11, 1.0098, 3.222 };
            Console.Write("源数据:");
            foreach (var item in arr)
            {
                Console.Write(item+"  ");
            }
            Console.WriteLine();
            ProcessArray(arr);
            Console.Write("改变后:");
            foreach (var item in arr)
            {
                Console.Write(item + "  ");
            }

            Console.ReadKey();
        }

        static void ProcessArray(double[] array)
        {
            Parallel.Invoke(
            () => ProcessPartialArray(array, 0, array.Length / 2),
            () => ProcessPartialArray(array, array.Length / 2, array.Length)
            );
        }
        static void ProcessPartialArray(double[] array, int begin, int end)
        {
            if (begin == 0 && end == array.Length / 2)
            {
                for (int i = 0; i < array.Length / 2; i++)
                {
                    array[i] = array[i] / 10;
                }
            }
            else if (begin == array.Length / 2 && end == array.Length)
            {
                for (int i = array.Length / 2; i < array.Length; i++)
                {
                    array[i] = array[i] * 10;
                }
            }
        }

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

如果在运行之前都无法确定调用的数量,就可以在Parallel.Invoke 函数中输入一个委托
数组:

 		static void DoAction20Times(Action action)
        {
            Action[] actions = Enumerable.Repeat(action, 20).ToArray();
            Parallel.Invoke(actions);
        }

就像Parallel 类的其他成员一样,Parallel.Invoke 也支持取消操作:

 		static void DoAction20Times(Action action, CancellationToken token)
        {
            Action[] actions = Enumerable.Repeat(action, 20).ToArray();
            Parallel.Invoke(new ParallelOptions { CancellationToken = token }, actions);
        }

对于简单的并行调用,Parallel.Invoke 是一个非常不错的解决方案。然而在以下两种情况中使Parallel.Invoke 并不是很合适:要对每一个输入的数据调用一个操作(改用Parallel.Foreach),或者每一个操作产生了一些输出(改用并行LINQ)。

动态并行:

问题: 并行任务的结构和数量要在运行时才能确定,这是一种更复杂的并行编程。

解决方案:任务并行库(TPL) :任务并行库(TPL)是以Task 类为中心构建的。Task 类的功能很强大,Parallel 类和并行LINQ 只是为了使用方便,从而对Task 类进行了封装。实现动态并行最简单的做法就是直接使用Task 类。

下面的例子对二叉树的每个节点进行处理,并且该处理是很耗资源的。二叉树的结构在运行时才能确定,因此非常适合采用动态并行。Traverse 方法处理当前节点,然后创建两个子任务,每个子任务对应一个子节点(本例中,假定必须先处理父节点,然后才能处理子节点)。ProcessTree 方法启动处理过程,创建一个最高层的父任务,并等待任务完成:

		void Traverse(Node current)
        {
            DoExpensiveActionOnNode(current);//执行复杂的操作在当前节点上
            if (current.Left != null)
            {
                Task.Factory.StartNew(() => Traverse(current.Left),
                CancellationToken.None,
                TaskCreationOptions.AttachedToParent,
                TaskScheduler.Default);
            }
            if (current.Right != null)
            {
                Task.Factory.StartNew(() => Traverse(current.Right),
                CancellationToken.None,
                TaskCreationOptions.AttachedToParent,
                TaskScheduler.Default);
            }
        }
        public void ProcessTree(Node root)
        {
            var task = Task.Factory.StartNew(() => Traverse(root),
            CancellationToken.None,
            TaskCreationOptions.None,
            TaskScheduler.Default);
            task.Wait();
        }

如果这些任务没有“父/ 子”关系,那可以使用任务延续(continuation)的方法,安排任务一个接着一个地运行。这里continuation 是一个独立的任务,它在原始任务结束后运行:

Task task = Task.Factory.StartNew(
	() => Thread.Sleep(TimeSpan.FromSeconds(2)),
	CancellationToken.None,
	TaskCreationOptions.None,
	TaskScheduler.Default);
Task continuation = task.ContinueWith(
	t => Trace.WriteLine("Task is done"),
	CancellationToken.None,
	TaskContinuationOptions.None,
	TaskScheduler.Default);
// 对continuation 来说,参数“t”相当于“task”

上面的例子使用了CancellationToken.None 【取消令(cancellationtoken)】 和TaskScheduler.Default【任务调度器(task scheduler)】。最好在StartNew和ContinueWith 中明确指定用TaskScheduler。

在动态并行中,通常以“父/ 子”的方式对任务进行安排,但也不是必须要这么做。把每个新任务存储在线程安全的集合中,然后用Task.WaitAll 等待所有任务完成,这种做法同样可行。

在并行处理中使用Task 类,和在异步处理中使用完全不同:
1:在并发编程中,Task 类有两个作用:作为并行任务,或作为异步任务。并行任务可以使用阻塞的成员函数,例如Task.Wait、Task.Result、Task.WaitAll 和Task.WaitAny。并行任务通常也使用AttachedToParent 来建立任务之间的“父/ 子”关系。并行任务的创建需要用Task.Run 或者Task.Factory.StartNew。

2:相反,异步任务应该避免使用阻塞的成员函数,而应该使用await、Task.WhenAll 和Task.WhenAny。异步任务不使用AttachedToParent,但可以通过await 另一个任务,建立一种隐式的“父/ 子”关系。

Parallel 能简化同步状态下的 Task ,但不等同于 Task默认行为。

也就是说在运行 Parallel 中的 For , ForEach 方法时,调用者线程(在实例中就是主线程)是被阻滞的。 Parallel 虽然将让任务交给 Task 去处理,即交给 CLR 线程池处理,不过调用者会一直等到线程池的相关工作全部完成。表示并行的静态类 Parallel 只提供了 Invoke 方法,而没有同时提供BeginInvoke 方法,这也从一定程度说明这个问题。

在使用 Task 时,我们经常使用的是: Start 方法( Task 也提供了 RunSynchronously),它不会阻滞调用者线程。

Dome3 实例:

 static void Main(string[] args)
        {
            Task t = new Task(() =>
              {
                  while(true)
                  {

                  }

            });
            t.Start();
            Console.WriteLine("主线程即将结束!");
            Console.ReadKey();
        }

在这里插入图片描述

使用 Parallel 相近的功能,主线程被阻滞。

static void Main(string[] args)
        {
          
            Parallel.Invoke(() =>
            {
                while(true)
                {

                }

            });
            Console.WriteLine("主线程即将结束!");
            Console.ReadKey();
        }

如果执行这段代码,永远不会有输出。

并行编程,意味者运行时在后台将任务分配到尽量多的CPU上,虽然它在后台使用 Task 进行管理,但这并不意味者它等同于异步。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值