• 4.0中的并行计算和多线程详解(二)


      多线程部分

      多线程在4.0中被简化了很多,仅仅只需要用到System.Threading.Tasks.::.Task类,下面就来详细介绍下Task类的使用。

      一、简单使用

      开启一个线程,执行循环方法,返回结果。开始线程为Start(),等待线程结束为Wait()。

    Code
    1.         /// <summary>
    2.         /// Task简单使用
    3.         /// </summary>
    4.         private void Demo1()
    5.         {
    6.             int i = 0;
    7.             Random r = new Random(DateTime.Now.Second);
    8.             Task t = new Task(() =>
    9.             {
    10.                 for (int v = 0; v < 100; v++)
    11.                     i += r.Next(100);
    12.             });
    13.             t.Start();
    14.             t.Wait();
    15.             Console.WriteLine("这是执行Task1后等待完成:" + i.ToString());
    16.             Console.ReadLine();
    17.         }

      比以前使用Thread方便多了吧。上面的例子是使用外部的变量获得结果,下面的例子是用Task<T>直接返回结果,当调用Result属性时,会自动等待线程结束,等同调用了Wait()。代码如下:

    Code
    1.         /// <summary>
    2.         /// Task带返回值
    3.         /// </summary>
    4.         private void Demo2()
    5.         {
    6.             Random r = new Random(DateTime.Now.Second);
    7.             Task<int> t = new Task<int>(() =>
    8.             {
    9.                 int i = 0;
    10.                 for (int v = 0; v < 100; v++)
    11.                     i += r.Next(100);
    12.                 return i;
    13.             });
    14.             t.Start();
    15.             Console.WriteLine("这是执行Task1获取返回值:" + t.Result.ToString());
    16.             Console.ReadLine();
    17.         }

      总结1:Task的使用比Thread简单很多,减少了同步,等待等等问题,唯一的遗憾是不支持Thread的IsBackground。

      结论1:如果不需要使用IsBackground,那么尽情的使用Task吧。

      二、线程执行完毕后调用另一个线程

      也就是两个线程,有序的执行,这里使用ContinueWith(),t执行完毕后再执行一个task方法,不多说了代码如下:

    Code
    1.         /// <summary>
    2.         /// Task 执行完毕后调用另一个Task
    3.         /// </summary>
    4.         private void Demo3()
    5.         {
    6.             Random r = new Random(DateTime.Now.Second);
    7.             Task<int> t = new Task<int>(() =>
    8.             {
    9.                 int i = 0;
    10.                 for (int v = 0; v < 100; v++)
    11.                     i += r.Next(100);
    12.                 return i;
    13.             });
    14.             t.ContinueWith((Task<int> task) =>
    15.             {
    16.                 Console.WriteLine("这是执行完毕Task1后继续调用Task2:" + task.Result.ToString());
    17.             });
    18.             t.Start();
    19.             Console.ReadLine();
    20.         }

      也可以直接链式的写下去,代码如下:

    Code
    1.         /// <summary>
    2.         /// Task 执行完毕后调用另一个Task(链式写法)
    3.         /// </summary>
    4.         private void Demo4()
    5.         {
    6.             Random r = new Random(DateTime.Now.Second);
    7.             Task<int> t = new Task<int>(() =>
    8.             {
    9.                 int i = 0;
    10.                 for (int v = 0; v < 100; v++)
    11.                     i += r.Next(100);
    12.                 return i;
    13.             });
    14.             Task t2 = t.ContinueWith((Task<int> task) =>
    15.             {
    16.                 Console.WriteLine(task.Result.ToString());
    17.             });
    18.             t2.ContinueWith(task =>
    19.             {
    20.                 Console.WriteLine("这是执行完毕Task1后继续调用Task2,Task2后调用Task3。");
    21.             });
    22.             t.Start();
    23.             Console.ReadLine();
    24.         }

      结论2:Task可以便捷的将几个方法串行执行。

      三、带有父子关系的线程/多线程并行开启

      t带有t1,t2,t3三个子线程,执行t的时候t1,t2,t3可并行处理,t必须等待t1,t2,t3都执行完毕后才能结束。创建子Task时候必须指定参数为AttachedToParent。

    Code
    1.         /// <summary>
    2.         /// 带有父子关系的Task集合,[TaskCreationOptions.AttachedToParent]
    3.         ///
    4.         /// 值                说明
    5.         /// None              默认值,此Task会被排入Local Queue中等待执行,采用LIFO模式。
    6.         /// AttachedToParent  建立的Task必须是外围的Task的子Task,也是放入Local Queue,採LIFO模式。
    7.         /// LongRunning       建立的Task不受Thread Pool所管理,直接新增一个Thread来执行此Task,无等待、无排程。
    8.         /// PreferFairness    建立的Task直接放入Global Queue中,採FIFO模式。(比上面的优先级低)
    9.         /// </summary>
    10.         private void Demo5()
    11.         {
    12.             Task<int> t = new Task<int>(() =>
    13.             {
    14.                 Task<int> t1 = new Task<int>(() =>
    15.                 {
    16.                     int i = 0;
    17.                     Random r = new Random(DateTime.Now.Second);
    18.                     for (int v = 0; v < 100; v++)
    19.                         i += r.Next(100);
    20.                     return i;
    21.                 }, TaskCreationOptions.AttachedToParent);
    22.                 Task<int> t2 = new Task<int>(() =>
    23.                 {
    24.                     int i = 0;
    25.                     Random r = new Random(DateTime.Now.Second);
    26.                     for (int v = 0; v < 100; v++)
    27.                         i += r.Next(100);
    28.                     return i;
    29.                 }, TaskCreationOptions.AttachedToParent);
    30.                 Task<int> t3 = new Task<int>(() =>
    31.                 {
    32.                     int i = 0;
    33.                     Random r = new Random(DateTime.Now.Second);
    34.                     for (int v = 0; v < 100; v++)
    35.                         i += r.Next(100);
    36.                     return i;
    37.                 }, TaskCreationOptions.AttachedToParent);
    38.                 t1.Start();
    39.                 t2.Start();
    40.                 t3.Start();
    41.                 return t1.Result + t2.Result + t3.Result;
    42.             });
    43.             t.Start();
    44.             t.Wait();
    45.             Console.WriteLine("这是带有父子关系的Task集合:" + t.Result.ToString());
    46.             Console.ReadLine();
    47.         }

      结论3:多个线程的同时开启在这里也很方便,也不用担心同步等问题。

      四、Task的中断

      这个很复杂,就不多说了,代码中有比较详细的介绍。

    Code
    1.         /// <summary>
    2.         /// 中途取消Task执行,Token
    3.         ///
    4.         /// 一是正常结束、二是产生例外、三是透过Cancel机制,这三种情况都会反映在Task.Status属性上
    5.         /// 值                              说明
    6.         /// Created                         Task已经建立,但未呼叫Start。
    7.         /// WaitingForActivation            Task已排入排程,但尚未执行(一般我们建立的Task不会有此状态,只有ContinueWith所产生的Task才会有此状态)。
    8.         /// WaitingToRun                    Task已排入排程,等待执行中。
    9.         /// Running                         Task执行中。
    10.         /// WaitingForChildrenToComplete    Task正等待子Task結束。
    11.         /// RanToCompletion                 Task已经正常执行完毕。
    12.         /// Canceled                        Task已被取消。
    13.         /// Faulted                         Task执行中发生未预期例外。
    14.         ///
    15.         /// 除了Status属性外,Task还提供了另外三个属性来判定Task状态。
    16.         /// 属性            说明
    17.         /// IsCompleted     Task已经正常执行完毕。
    18.         /// IsFaulted       Task执行中法生未预期例外。
    19.         /// IsCanceled      Task已被取消。
    20.         /// </summary>
    21.         private void Demo6()
    22.         {
    23.             CancellationTokenSource cts = new CancellationTokenSource();
    24.             var ctoken = cts.Token;
    25.             Task t1 = new Task(() =>
    26.             {
    27.                 for (int v = 0; v < 10; v++)
    28.                 {
    29.                     if (ctoken.IsCancellationRequested)
    30.                     {
    31.                         //第一种写法
    32.                         //这个会抛出异常
    33.                         ctoken.ThrowIfCancellationRequested();
    34.                         //另一种写法
    35.                         //这个不会返回异常,但是获取不到是否是中断还是执行完毕。
    36.                         //return;
    37.                     }
    38.                     Thread.Sleep(1000);
    39.                     Console.WriteLine(v);
    40.                 }
    41.             }, ctoken);
    42.             t1.Start();
    43.             Thread.Sleep(2000);
    44.             cts.Cancel();
    45.             try
    46.             {
    47.                 t1.Wait();
    48.             }
    49.             catch
    50.             {
    51.                 if (t1.IsCanceled)
    52.                     Console.WriteLine("cancel");
    53.             }
    54.             Console.ReadLine();
    55.             cts.Dispose();
    56.         }

      结论4:中断很复杂,但是对一般逻辑来说,是没有很大必要的。

      五、其他

      这里介绍下另一种写法Task.Factory,以及ContinueWhenAny和ContinueWhenAll两个方法。Task.Factory是静态工厂类,用于对Task提供一些麻烦的支持,这里主要介绍ContinueWhenAny和ContinueWhenAll。

    ContinueWhenAll所指定的函式会在传入的所有Tasks结束时执行,只会执行一次。ContinueWhenAny所指定的函式会在传入的Tasks中有任何一个结束时执行,且与ContinueWhenAll相同,只会执行一次。好了,还是看代码:

    Code
    1.         /// <summary>
    2.         /// Task.Factory
    3.         /// ContinueWhenAny和ContinueWhenAll
    4.         /// ContinueWhenAll所指定的函式会在传入的所有Tasks结束时执行,只会执行一次。
    5.         /// ContinueWhenAny所指定的函式会在传入的Tasks中有任何一个结束时执行,且与ContinueWhenAll相同,只会执行一次。
    6.         /// </summary>
    7.         private void Demo7()
    8.         {
    9.             Task<int> t1 = Task.Factory.StartNew<int>(() =>
    10.             {
    11.                 int total = 0;
    12.                 for (int i = 0; i < 10; i++)
    13.                     total += i;
    14.                 Thread.Sleep(12000);
    15.                 return total;
    16.             });
    17.             Task<int> t2 = Task.Factory.StartNew<int>(() =>
    18.             {
    19.                 int total = 0;
    20.                 for (int i = 10; i < 20; i++)
    21.                     total += i;
    22.                 Thread.Sleep(10000);
    23.                 return total;
    24.             });
    25.             Task tfinal = Task.Factory.ContinueWhenAny<int>(
    26.                          new Task<int>[] { t1, t2 }, (Task<int> task) =>
    27.                          {
    28.                              if (task.Status == TaskStatus.RanToCompletion)
    29.                              {
    30.                                  Console.WriteLine(task.Result);
    31.                              }
    32.                          });
    33.             Console.ReadLine();
    34.         }

      结论5:ContinueWhenAny和ContinueWhenAll对特定条件执行,还是有些用处的。

      好了,这篇文章算是完结了,4.0中的并行和线程确实简单了很多,使用起来也很方便,为了性能的提升还是要适当的使用下。

  • 相关阅读:
    C# Linq Enumerable 技巧
    Winform 踩坑
    BootStrap Table
    java8+junit5实现并发测试(多线程)
    Junit5+REST-assured 做接口测试
    log4j的使用
    ASP.NET项目启用SSL
    hyper-v虚拟机内存占用过高
    C#使用qq邮箱的smtp服务发邮件
    CALayer设置圆角
  • 原文地址:https://www.cnblogs.com/Ruiky/p/2485933.html
Copyright © 2020-2023  润新知