C#异步编程2

Parallel类

自.NET4以来.NET提供线程的一个抽线机制:任务。Parallel类是对线程一个很好的抽象。该类位于System.Threading.Tasks命名空间中,提供了数据和任务的并行性。如果需要数据并行(多个线程协同处理同一批数据)或任务并行(多个任务处理不同逻辑功能),而不需要更多的控制与线程间的交互,可以使用Parallel。下面介绍Parallel常用用法。

Parallel.For()

Parallel定义了并行的for静态方法。对于C#的for语言而言,循环在单个线程中运行。而Parallel类的for方法则使用了多任务,每迭代一次都会从线程池申请一个空闲线程来执行指定代码块。Parallel.For()方法在每次迭代中执行相同代码段,迭代顺序没有明确定义,用于数据并行性。下面举两个例子说明:

  • Parallel.For()的用法
  class Program
    {
        static void Main(string[] args)
        {
            ParallelFor();
            Console.Read();
        }

        public static void ParallelFor()
        {
            var result = Parallel.For(0, 10, i =>//i从0到9执行10次
            {
                Task.Delay(1000).Wait();
                Log($"E{i}");
            });
            Console.WriteLine($"Is cpmplete:{result.IsCompleted }");
        }

        public static void Log(string prefix)
        {
            Console.WriteLine($"{prefix}, task:{Task.CurrentId},threadid: {Thread.CurrentThread.ManagedThreadId}");
        }
    } 

运行结果如下,可以看出每迭代一次都是启动一个新线程执行,而下图中线程ID为8出现两次,这是由线程池分配的空闲线程,由不得我们控制。
在这里插入图片描述

  • 下面特别介绍Parallel.For< T >()的用法,Parallel.For()方法的泛型版本还接受3个委托参数,分别为
    1.Func< TLocal > localInit,该委托只要用于为每个迭代任务初始化,返回一个泛型
    2.Func< int,ParallelLoopState,TLocal,TLocal > body,改委托接受迭代下标、停用迭代的一个ParallelLoopState对象(**如果想提前停止迭代,可传入该对象并调用该对象的Break()方法即可)、以及上一个迭代的返回值,TLocal线程安全
    3.Action< TLocal > localFinally,最后收尾处理,接受上一个迭代的返回值,TLocal线程安全
    {
        static void Main(string[] args)
        {
            ParallelForWithInit();
            Console.Read();
        }

        public static void ParallelForWithInit()
        {
            var result = Parallel.For<string>(0, 2, () =>//对每个线程初始化返回值可在第4个参数(委托)中的str1获取到
            {
                Log($"init thread");
                return $"{Thread.CurrentThread.ManagedThreadId}";
            },
            (i, pls, str1) =>// Func<int, ParallelLoopState, TLocal, TLocal> 类型,i为迭代下标,pls控制循环停止,str1为For第2参数(委托)的返回值
            {
                 Log($"body i={i} str1={str1}");
                 Task.Delay(100).Wait();
                 return $"{i}";
             },
            (str1) =>//Action<TLocal> 类型,str1为For第3参数(委托)的返回值
            {
                Log($"finally str1={str1}");
            });
            Console.WriteLine($"Is cpmplete:{result.IsCompleted }");
        }

        public static void Log(string prefix)
        {
            Console.WriteLine($"{prefix}, task:{Task.CurrentId},threadid: {Thread.CurrentThread.ManagedThreadId}");
        }
    } 

从下图可以看出线程id为1的三个委托中,body的str1是init的返回值,finally的str1是body的返回值,线程安全。通过这个功能,这个方法完美地累加了大量数据集合的结果。
在这里插入图片描述

Parallel.ForEach()

相信了解过C#语言的Foreach关键字的同学,应该能猜出Parallel.ForEach()这个静态方法的用途了吧,咱就不多做解释,直接看例子。

    class Program
    {
        static void Main(string[] args)
        {
            ParallelForWithInit();
            Console.Read();
        }

        public static void ParallelForWithInit()
        {
            List<int> data = new List<int> { 1, 2, 3, 4, 5, 6 };
            var result = Parallel.ForEach<int>(data, i =>
            {
                Log($"data={i}");

            });
            Console.WriteLine($"Is cpmplete:{result.IsCompleted }");
        }

        public static void Log(string prefix)
        {
            Console.WriteLine($"{prefix}, task:{Task.CurrentId},threadid: {Thread.CurrentThread.ManagedThreadId}");
        }
    } 

看下图运行结果,可以分析出ForEach方法对data进行遍历并从线程池申请空闲线程去打印,顺序没有明确定义。 在这里插入图片描述

Parallel.Invoke()

细心的同学应该发现了,不管是For还是Foreach,都只能迭代执行一个委托,那么下面我们介绍Parallel的另一个支持多个委托的静态方法Invoke,Invoke允许传递一个Action数组。

    class Program
    {
        static void Main(string[] args)
        {
            ParallelForWithInit();
            Console.Read();
        }

        public static void ParallelForWithInit()
        {
            Parallel.Invoke(Log1, Log2, Log3);
        }

        public static void Log1()
        {
            Console.WriteLine($"Log1:task:{Task.CurrentId},threadid: {Thread.CurrentThread.ManagedThreadId}");
        }
        public static void Log2()
        {
            Console.WriteLine($"Log2:task:{Task.CurrentId},threadid: {Thread.CurrentThread.ManagedThreadId}");
        }
        public static void Log3()
        {
            Console.WriteLine($"Log3:task:{Task.CurrentId},threadid: {Thread.CurrentThread.ManagedThreadId}");
        }
    }

如下图运行结果,每一个Action委托都是一个单独的线程在执行。
在这里插入图片描述

Parallel总结

Parallel类是.Net对线程的一个很好的抽象,主要提供了For、Foreach和Invoke三个静态方法的支持。For、Foreach适合数据并发执行,只能接受一个委托。而Invoke适合任务并行模式,亦即可接受多个不同的无参Action。

Task类

Task类作是.NET Framework 4新增加的异步操作类,极大的方便了对多线程和异步操作的代码编写。Task类相比之前使用委托进行异步操作和使用ThreadPool线程池进行多线程,不仅极大简化了代码编写,而且添加了许多实用的方法和良好的状态管理机制。

Task任务启动的几种写法

  • 异步启动任务,每启动一个Task任务,底层都会到线程池申请一个空闲线程来完成指定委托。
   class Program
   {
       static async Task Main(string[] args)
       {
           //方式1
           var t1 = Task<string>.Run(() => {
               Console.WriteLine($"方式1 的线程ID为{Thread.CurrentThread.ManagedThreadId}");
               return "方式1"; });
           //方式2
           var t2 = Task<string>.Factory.StartNew(() => {
               Console.WriteLine($"方式2 的线程ID为{Thread.CurrentThread.ManagedThreadId}"); 
               return "方式2"; });
           //方式3
           var t3 =new TaskFactory <string>().StartNew(() => {
               Console.WriteLine($"方式3 的线程ID为{Thread.CurrentThread.ManagedThreadId}"); 
               return "方式3"; });
           //方式4
           var t4 = new Task<string>(() => {
               Console.WriteLine($"方式4 的线程ID为{Thread.CurrentThread.ManagedThreadId}");
               return "方式4"; });
           t4.Start();
           Console.WriteLine($"主线程 的线程ID为{Thread.CurrentThread.ManagedThreadId}");
           Console.Read();
       }
   }  

我们看到先打印“主线程。。。”,然后再打印各个任务,说明了Task不会阻塞主线程。
在这里插入图片描述

  • 同步启动任务,任务不一定使用线程池中的线程来执行,也可以使用其他线程。
    class Program
    {
        static async Task Main(string[] args)
        {
            var t = new Task<string>(() =>
            {
                Console.WriteLine($"任务线程ID为{Thread.CurrentThread.ManagedThreadId}");
                return "完成";
            });
            t.RunSynchronously();
            Console.WriteLine($"主线程 的线程ID为{Thread.CurrentThread.ManagedThreadId}");
            Console.Read();
        }
    }  

通过结果可以看出两个任务线程ID一致,RunSynchronously方法阻塞了主线程,并通过主线程执行了任务。
在这里插入图片描述

  • 如果任务将长时间运行,Task支持同TaskCreationOptions.LongRunning告诉任务调度器创建一个新线程,而不是从线程池中申请使用线程,此时的线程可以不受线程池管理。当新任务向线程池申请线程时,线程池会选择等待其他任务执行完成后释放的线程,而不是重新创建。而对于长时间运行任务的新线程,任务调度器会立刻知道等待它们执行完成是没结果的,会过滤掉这类线程去等待其他任务线程释放。
    class Program
    {
        static async Task Main(string[] args)
        {
            var t = new Task<string>(() =>
            {
                Console.WriteLine($"线程ID为{Thread.CurrentThread.ManagedThreadId},是否是托管于线程池:{Thread.CurrentThread.IsThreadPoolThread}");
                return "完成";
            }, TaskCreationOptions.LongRunning);
            t.Start();
            Console.WriteLine($"主线程 的线程ID为{Thread.CurrentThread.ManagedThreadId}");
            Console.Read();
        }
    }  

在这里插入图片描述
Task接受Action委托,泛型Task接受Func 委托

Task同步任务启动的写法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值