zoukankan      html  css  js  c++  java
  • 如何取消后台线程的执行

    介绍

    在使用多线程模型进行编程时,经常遇到的问题之一是,当我们关闭前台的UI线程时,后台的辅助线程仍然处于活动状态,从而导致整个应用程序无法正常退出。这时我们需要一种较安全的方式来结束后台线程的运行,这样我们可以随时结束后台线程的运行,并且在线程结束时进行相应的资源清理工作(例如将内存数据写入硬盘)。.net框架提供了一些工具来实现该功能。

    目录

    • IsBackground属性
    • Abort方法
    • 轮循方式
    • 取消阻塞的线程

    IsBackgound属性

    Thread类提供了IsBackground属性,当线程的IsBackground属性被设置为true时,表示此线程为后台工作线程。当一个应用程序结束时,它的所有后台线程会自动的被结束执行。如果你有一个后台线程侦听Socket连接,并且正在被阻塞,那么这时候通过设置线程的IsBackground属性为True,使它自动随应用程序的结束而结束是比较合适的。但在这种情况下,线程会静悄悄的结束,它不会引发任何异常,你的线程没有机会执行一些需要的清理代码。例如,内存中的数据可能会来不及写入磁盘,从而造成丢失数据。

    Abort方法

    可以调用Thread类的Abort方法来强制终制线程。上调用此方法时,线程上引发ThreadAbortException,并导至线程终结,通过捕获该异常,可以执行一些资源清理代码。但这种模式也有一些问题,主要是难以知道线程上的代码执行到什么地方,所有相应的资源清理代码也难以编写。总的来说这是一种比较粗暴的终止线程执行的方法,通常来说是不推荐使用的。

    轮循方式

    如果后台线程将执行一个很长的计算,那么可以将计算隔成若干小段,并经常检查是否需要取消线程。.NET框架提供了CancellationTokenSource类来作为线程取消的统一模式。例如:

     public class Example
        {
            public static void Main()
            {
                CancellationTokenSource cts = new CancellationTokenSource();
                var thread = new Thread(ThreadWork);
                thread.Start(cts.Token);
                while (true)
                {
                    if(Console.ReadKey().KeyChar == 'c')
                    {
                        Console.WriteLine("请求取消线程的执行");
                        cts.Cancel();
                        break;
                    }
                }
                Console.ReadLine();
            }
    
            private static void ThreadWork(object state)
            {
                CancellationToken cancellationToken = (CancellationToken)state;
    
                while (true)
                {
                    // 检查是否取消
                    if(cancellationToken.IsCancellationRequested)
                    {
                        Console.WriteLine("线程已经取消了");
                        Console.WriteLine("线程的资源已经清理完成。");
                        break;
                    }
                    // 模拟工作
                    Thread.SpinWait(500000);
                    Console.WriteLine("我还在工作。");
                }
            }
        }

    取消阻塞的线程

    上面的示例中,后台线程会长时间进行计算,但更多的时候,线程会由于等待某个事件,从而进入阻塞状态。这个时候,实际上线程已经不再执行状态了,很明显,它没有机会去检查取消标志。 那么,该如何解决这个问题呢?CancellationToken的WaitHandle属性提供了解答。WaitHandle类有一个静态方法WaitAny,它可以同时等待多个事件,当多个事件中的任意一个有效时,线程都会从阻塞状态中返回。可以根据WaitAny方法的返回值来判断发生了什么事件,从而相应的执行代码。例子:

        public class Example
        {
            private static int Value;
    
            public static void Main()
            {
                var autoResetEvent = new AutoResetEvent(false);
                var cts = new CancellationTokenSource();
                var state = new { ValueAvailableEvent = autoResetEvent, CancellationToken = cts.Token };
                var threadConsumer = new Thread(ConsumerThreadWork);
                var threadProducter = new Thread(ProducterThreadWork);
    
                threadConsumer.Start(state);
                threadProducter.Start(state);
    
                while (true)
                {
                    if (Console.ReadKey().KeyChar == 'c')
                    {
                        Console.WriteLine("请求取消线程的执行");
                        cts.Cancel();
                        break;
                    }
                }
                Console.ReadLine();
    
            }
            public static void ProducterThreadWork(dynamic state)
            {
                var valueAvailableEvent = (AutoResetEvent)state.ValueAvailableEvent;
                var cancellationToken = (CancellationToken)state.CancellationToken;
                var rand = new Random();
                while (!cancellationToken.IsCancellationRequested)
                {
                    Value = rand.Next();
                    Console.WriteLine("\r\n产生一个值{0}", Value);
                    valueAvailableEvent.Set();
                    Thread.Sleep(500);
                }
    
                Console.WriteLine("生产者线程被取消。");
            }
    
            public static void ConsumerThreadWork(dynamic state)
            {
                var valueAvailableEvent = (AutoResetEvent)state.ValueAvailableEvent;
                var cancellationToken = (CancellationToken)state.CancellationToken;
                var events = new[] { valueAvailableEvent, cancellationToken.WaitHandle };
    
                while (true)
                {
                    var eventIndex = WaitHandle.WaitAny(events);
                    // 处理数据
                    if (eventIndex == 0)
                    {
                        Console.WriteLine("处理值{0}。", Value);
                    }
                    // 处理取消事件
                    else if (eventIndex == 1)
                    {
                        Console.WriteLine("消费者线程被取消。");
                        break;
                    }
                }
            }
        }

    在上面的例子中,有三个线程,分别是UI线程,生产者线程和消费者线程。其中生产者线程每隔一秒产生一个有效数值,并将数据保存到Value字段中,而消费者线程等待值的产生,这个等待的过程是阻塞的。消费都线程通过WaitHandle.WaitAny方法来同时等待值有效事件或者取消事件,当任意一个事件有效时,线程都将继续,并且通过返回的值来判断发生的事件,并作相应的处理。

    总结

    多线程模型中的线程取消问题还是比较复杂的。Thread.IsBackground属性提供了在前台线程结束后自动结束线程的方法。Thread.Abort方法提供了一种“粗暴”的结束线程的方法。CancellationTokenSource类则是线程取消的标准模式,我们应当更多的使用这种模式。文章写的不多,基本是字数不够,代码来凑,大家伙将就的看看吧。

  • 相关阅读:
    js中的原生Ajax和JQuery中的Ajax
    this的用法
    static的特性
    时政20180807
    java compiler没有1.8怎么办
    Description Resource Path Location Type Java compiler level does not match the version of the installed Java project facet Unknown Faceted Project Problem (Java Version Mismatch)
    分词器
    [数算]有一个工程甲、乙、丙单独做,分别要48天、72天、96天完成
    一点感想
    解析Excel文件 Apache POI框架使用
  • 原文地址:https://www.cnblogs.com/shangfc/p/2762777.html
Copyright © 2011-2022 走看看