zoukankan      html  css  js  c++  java
  • 使用Task的一些知识优化了一下同事的多线程协作取消的一串代码

      最近在看一个同事的代码,代码的本意是在main方法中开启10个线程,用这10个线程来处理一批业务逻辑,在某一时刻当你命令console退出的时候,这个

    时候不是立即让console退出,而是需要等待10个线程把检测状态之后的业务逻辑执行完之后再退出,这样做是有道理的,如果强行退出会有可能造成子线程的业

    务数据损坏,没毛病吧,业务逻辑大概就是这样。

    一:现实场景

    由于真实场景的代码比较复杂和繁琐,为了方便演示,我将同事所写的代码抽象一下,类似下面这样,看好了咯~~~

     1 class Program
     2     {
     3         private static int workThreadNums = 0;
     4 
     5         private static bool isStop = false;
     6 
     7         static void Main(string[] args)
     8         {
     9             var tasks = new Task[10];
    10 
    11 
    12             for (int i = 0; i < 10; i++)
    13             {
    14                 tasks[i] = Task.Factory.StartNew((obj) =>
    15                 {
    16                     Run();
    17                 }, i);
    18             }
    19 
    20             //是否退出
    21             string input = Console.ReadLine();
    22 
    23             while ("Y".Equals(input, StringComparison.OrdinalIgnoreCase))
    24             {
    25                 break;
    26             }
    27 
    28             isStop = true;
    29 
    30             while (workThreadNums != 0)
    31             {
    32                 Console.WriteLine("正在等待线程结束,当前还在运行线程有:{0}", workThreadNums);
    33 
    34                 Thread.Sleep(10);
    35             }
    36             Console.WriteLine("准备退出了。。。");
    37             Console.Read();
    38             Environment.Exit(0);
    39         }
    40 
    41         static void Run()
    42         {
    43             try
    44             {
    45                 workThreadNums++;
    46 
    47                 while (true)
    48                 {
    49                     if (isStop) break;
    50 
    51                     Thread.Sleep(1000);
    52 
    53                     //执行业务逻辑
    54                     Console.WriteLine("我是线程:{0},正在执行业务逻辑", Thread.CurrentThread.ManagedThreadId);
    55                 }
    56             }
    57             finally
    58             {
    59                 workThreadNums--;
    60             }
    61         }
    62     }

          其实扫一下上面的代码应该就知道是用来干嘛的,业务逻辑没毛病,基本可以实现刚才的业务场景,在console退出的时候可以完全确保10个线程都把自己的业

    务逻辑处理完毕了。不过从美观角度上来看,这种代码就太low了。。。一点档次都没有,比如存在下面两点问题:

    第一点:局部变量太多,又是isStop又是workThreaNums,导致业务逻辑Run方法中掺杂了很多的非业务逻辑,可读性和维护性都比较low。

    第二点:main函数在退出的时候用while检测workThreadNums是否为“0”,貌似没问题,但仔细想想这段代码有必要吗?

    接下来我把代码跑一下,可以看到这个while检测到了在退出时的workThredNums的中间状态“7”,有点意思吧~~~

    二:代码优化

      那上面这段代码怎么优化呢?如何踢掉业务逻辑方法中的非业务代码呢?当然应该从业务逻辑上考虑一下了,其实这个问题的核心就是两点:

    1. 如何实现多线程中的协作取消?

    2. 如何实现多线程整体执行完毕通知主线程?

    这种场景优化千万不要受到前人写的代码所影响,最好忘掉就更好了,不然你会下意识的受到什么workthreadnums,isstop这些变量的左右,不说废话了,如

    果你对task并发模型很熟悉的话,你的优化方案很快就会出来的。。。

    1. 协作取消:

        直接用一个bool变量来判断子线程是否退出的办法其实是很没有档次的,在net 4.0中有一个类(CancellationTokenSource)专门来解决使用bool变量来判

    断的这种很low的场景,而且比bool变量具有更强大的功能,这个会在以后的文章中跟大家去讲。

    2. 多线程整体执行完毕通知主线程

        目前我们看到的方式是主线程通过轮询workthreadnums这种没有档次的方式去做的,其实这种方式本质上就是任务串行,而如果你明白task的话,你就知道

    有很多的手段是执行任务串行的,比如什么ContinueWith,WhenAll,WhenAny等等方式,所以你只需要将一组task串联到WhenAll之后就可以了。好了,上

    面就是我的解决思路,接下来看一下代码吧:

     1  class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             CancellationTokenSource source = new CancellationTokenSource();
     6 
     7             var tasks = new Task[10];
     8 
     9             for (int i = 0; i < 10; i++)
    10             {
    11                 tasks[i] = Task.Factory.StartNew((m) =>
    12                 {
    13                     Run(source.Token);
    14                 }, i);
    15             }
    16 
    17             Task.WhenAll(tasks).ContinueWith((t) =>
    18             {
    19                 Console.WriteLine("准备退出了。。。");
    20                 Console.Read();
    21                 Environment.Exit(0);
    22             });
    23 
    24             string input = Console.ReadLine();
    25             while ("Y".Equals(input, StringComparison.OrdinalIgnoreCase))
    26             {
    27                 source.Cancel();
    28             }
    29 
    30             Console.Read();
    31         }
    32 
    33         static void Run(CancellationToken token)
    34         {
    35             while (true)
    36             {
    37                 if (token.IsCancellationRequested) break;
    38 
    39                 Thread.Sleep(1000);
    40 
    41                 //执行业务逻辑
    42                 Console.WriteLine("我是线程:{0},正在执行业务逻辑", Thread.CurrentThread.ManagedThreadId);
    43             }
    44         }
    45     }

           单从代码量上面看就缩减了17行代码,而且业务逻辑也非常的简单明了,然后再看业务逻辑方法Run,其实你根本就不需要所谓的workThreadNums++,--

    的操作,而且多线程下不用锁的话,还容易出现竞态的问题,解决方案就是使用WhenAll等待一组Tasks完成任务,之后再串行要退出的Task任务,是不是很完美,

    而协作取消的话,只需将取消的token传递给业务逻辑方法,当主线程执行source.Cancel()方法取消的时候,子线程就会通过IsCancellationRequested感知到主

    线程做了取消操作。

    好了,就说这么多吧,还是那句话,”因为我们视野的不开阔,导致缺乏解决问题的手段“,所以古话说得好,磨刀不误砍柴工。。。

  • 相关阅读:
    对宏的另外一些认识 及 assert.h的实现细节
    不要想太多
    线段树
    SQL基础 利用SELECT检索数据
    hidden表单值无法重置的缺陷
    oracle 数据库登陆
    基于ejb3,对JDBC进行封装,让使用JDBC时能像hibernate使用annotation注解一样简便,而且更加轻巧
    GoJS的一些使用技巧
    GoJS的学习使用
    灵活使用trim方法
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/6541220.html
Copyright © 2011-2022 走看看