Parallel的使用 之Parallel.Invoke

在Parallel下面有三个常用的方法invoke,for和forEach。
首先感受一下3个方法的调用情况:

// For

Parallel.For(0, 1000, (i) =>
            {
                //i是从0开始一直到1000结束

            });

       // ForEach

            var lst = new List<string>();

            Parallel.ForEach(lst, (s) =>
            {
                //do something
            });

            //Invoke
            Parallel.Invoke(
                () => { },
                () => { },
                () => { }
                );   

1:  Parallel.Invoke

    这是最简单,最简洁的将串行的代码并行化。

例如:可以这样调用  

internal static void ParallelMothed()
        { 

           Parallel.Invoke(Run1, Run2);  //这里的Run1 Run2 都是方法。

        }

    static void Run1()
        {
            Console.WriteLine("我是任务一,我跑了3s");
            Thread.Sleep(3000);
        }

        static void Run2()
        {
            Console.WriteLine("我是任务二,我跑了5s");
            Thread.Sleep(5000);
        }

在这个例子中可以获取二点信息:

第一:一个任务是可以分解成多个任务,采用分而治之的思想。

第二:尽可能的避免子任务之间的依赖性,因为子任务是并行执行,所以就没有谁一定在前,谁一定在后的规定了。

第三:主线程必须等Invoke中的所有方法执行完成后返回才继续向下执行;这样对我们以后设计并行的时候,要考虑每个Task任务尽可能差不多,如果相差很大,比如一个时间非常长,其他都比较短,这样一个线程可能会影响整个任务的性能。这点非常重要

第四:没有固定的顺序,每个Task可能是不同的线程去执行,也可能是相同的;

如果调用的方法是有参数的,如何处理?同理,直接带上参数就可以

 

Parallel.Invoke(() => Task1("task1"), () => Task2("task2"), () => Task3("task3"));

例如:

class ParallelInvoke
    {
        // Invoke带参数 调用 
        public void Client1()
        {
            Stopwatch stopWatch = new Stopwatch();

            Console.WriteLine("主线程:{0}线程ID : {1};开始", "Client1", Thread.CurrentThread.ManagedThreadId);
            stopWatch.Start();
            Parallel.Invoke(() => Task1("task1"), () => Task2("task2"), () => Task3("task3"));
            stopWatch.Stop();
            Console.WriteLine("主线程:{0}线程ID : {1};结束,共用时{2}ms", "Client1", Thread.CurrentThread.ManagedThreadId, stopWatch.ElapsedMilliseconds);
            Console.ReadKey();
        }

        private void Task1(string data)
        {
            Thread.Sleep(5000);
            Console.WriteLine("任务名:{0}线程ID : {1}", data, Thread.CurrentThread.ManagedThreadId);
        }

        private void Task2(string data)
        {
            Console.WriteLine("任务名:{0}线程ID : {1}", data, Thread.CurrentThread.ManagedThreadId);
        }

        private void Task3(string data)
        {
            Console.WriteLine("任务名:{0}线程ID : {1}", data, Thread.CurrentThread.ManagedThreadId);
        }
    }
}
 

 

 

 

执行运行后结果:

ps:如果其中有一个异常怎么办? 带做这个问题修改了增加了一个Task4.

public class ParallelInvoke
    {
        /// <summary>
        /// Invoke方式一 action
        /// </summary>
        public  void Client1()
        {
            Stopwatch stopWatch = new Stopwatch();
           
            Console.WriteLine("主线程:{0}线程ID : {1};开始", "Client1", Thread.CurrentThread.ManagedThreadId);
            stopWatch.Start();

            try
            {
                Parallel.Invoke(() => Task1("task1"), () => Task2("task2"), () => Task3("task3"), delegate () { throw new Exception("我这里发送了异常"); });
            }
            catch (AggregateException ae)
            {
                foreach (var ex in ae.InnerExceptions)
                    Console.WriteLine(ex.Message);
            }
          
            stopWatch.Stop();
            Console.WriteLine("主线程:{0}线程ID : {1};结束,共用时{2}ms", "Client1", Thread.CurrentThread.ManagedThreadId, stopWatch.ElapsedMilliseconds);
        }
   }

主要看 delegate() { throw new Exception("我这里发送了异常");} 增加了这个委托Task3. 然后我们看结果:

 这里我们发现即使有异常程序也会完成执行,而且不会影响其他Task的执行。

 

在异步的时候可以获取当前线程的id:代码如:Thread.CurrentThread.ManagedThreadId)

  3、 重载方法ParallelOptions 的使用。

   理解ParallelOptions建议大家异步编程:轻量级线程同步基元对象   讲的非常详细。

   主要理解两个参数:

     CancellationToken     控制线程的取消
     MaxDegreeOfParallelism  设置最大的线程数,有时候可能会跑遍所有的内核,为了提高其他应用程序的稳定性,就要限制参与的内核

     下面从代码上看效果如何?

public class ParallelInvoke
    {
  // 定义CancellationTokenSource 控制取消
        readonly CancellationTokenSource _cts = new CancellationTokenSource();

        /// <summary>
        /// Invoke方式一 action
        /// </summary>
        public  void Client1()
        {
             Console.WriteLine("主线程:{0}线程ID : {1};开始{2}", "Client3", Thread.CurrentThread.ManagedThreadId, DateTime.Now);
            
            var po = new ParallelOptions
            {
                CancellationToken = _cts.Token, // 控制线程取消
                MaxDegreeOfParallelism = 3  // 设置最大的线程数3,仔细观察线程ID变化
            };
           
            Parallel.Invoke(po, () => Task1("task1"), ()=>Task5(po), Task6);
            
            Console.WriteLine("主线程:{0}线程ID : {1};结束{2}", "Client3", Thread.CurrentThread.ManagedThreadId, DateTime.Now);
        }

        private void Task1(string data)
        {
            Thread.Sleep(5000);
            Console.WriteLine("任务名:{0}线程ID : {1}", data, Thread.CurrentThread.ManagedThreadId);
        }

        // 打印数字
        private void Task5(ParallelOptions po)
        {
            Console.WriteLine("进入Task5线程ID : {0}", Thread.CurrentThread.ManagedThreadId);
            int i = 0;
          while (i < 100)
            {
                // 判断是否已经取消
                if (po.CancellationToken.IsCancellationRequested)
                {
                    Console.WriteLine("已经被取消。");
                    return;
                }

                Thread.Sleep(100);
                Console.Write(i + " ");
                Interlocked.Increment(ref i);
            }

        }

        /// <summary>
        /// 10秒后取消
        /// </summary>
        private void Task6()
        {
            Console.WriteLine("进入取消任务,Task6线程ID : {0}", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000 * 10);
            _cts.Cancel();
            Console.WriteLine("发起取消请求...........");
        }

执行结果:

     从程序结果我们看到以下特点:

            1、程序在执行过程中线程数码不超过3个。

            2、CancellationTokenSource/CancellationToken控制任务的取消。

四、总结

      Parallel.Invoke 的使用过程中我们要注意以下特点:

      1、没有特定的顺序,Invoke中的方法全部执行完才返回,但是即使有异常在执行过程中也同样会完成,他只是一个很简单的并行处理方法,特点就是简单,不需要我们考虑线程的问题。

      2、如果在设计Invoke中有个需要很长时间,这样会影响整个Invoke的效率和性能,这个我们在设计每个task时候必须去考虑的。

      3、Invoke 参数是委托方法。

      4、当然Invoke在每次调用都有开销的,不一定并行一定比串行好,要根据实际情况,内核环境多次测试调优才可以。

      5、异常处理比较复杂。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值