zoukankan      html  css  js  c++  java
  • [.Net线程处理系列]专题二:线程池中的工作者线程

    目录:

    一、上节补充

    二、CLR线程池基础

    三、通过线程池的工作者线程实现异步

    四、使用委托实现异步

    五、任务

    一、上节补充

    对于Thread类还有几个常用方法需要说明的。

    1.1 Suspend和Resume方法

    这两个方法在.net Framework 1.0的时候就支持的方法,他们分别可以挂起线程和恢复挂起的线程。但在.net Framework 2.0以后的版本中这两个方法都过时了,MSDN的解释是这样:

    警告:

    不要使用 Suspend 和 Resume 方法来同步线程的活动。您无法知道挂起线程时它正在执行什么代码。如果您在安全权限评估期间挂起持有锁的线程,则 AppDomain中的其他线程可能被阻止。如果您在线程正在执行类构造函数时挂起它,则 AppDomain中尝试使用该类的其他线程将被阻止。这样很容易发生死锁。

    对于这个解释可能有点抽象吧,让我们来看看一段代码可能会清晰点:

    复制代码
     class Program
        {
            static void Main(string[] args)
            {
                // 创建一个线程来测试
                Thread thread1 = new Thread(TestMethod);      
                thread1.Name = "Thread1";   
                thread1.Start();    
                Thread.Sleep(2000);
                Console.WriteLine("Main Thread is running");
                ////int b = 0;
                ////int a = 3 / b;
                ////Console.WriteLine(a);
                thread1.Resume();     
                Console.Read();
            }
    
            private static void TestMethod()
            {     
                Console.WriteLine("Thread: {0} has been suspended!", Thread.CurrentThread.Name);
          
                //将当前线程挂起
                Thread.CurrentThread.Suspend();          
                Console.WriteLine("Thread: {0} has been resumed!", Thread.CurrentThread.Name);
            }
        }
    复制代码

    在上面这段代码中thread1线程是在主线程中恢复的,但当主线程发生异常时,这时候就thread1一直处于挂起状态,此时thread1所使用的资源就不能释放(除非强制终止进程),当另外线程需要使用这快资源的时候, 这时候就很可能发生死锁现象。

    上面一段代码还存在一个隐患,请看下面一小段代码:

    复制代码
     class Program
        {
            static void Main(string[] args)
            {
                // 创建一个线程来测试
                Thread thread1 = new Thread(TestMethod);      
                thread1.Name = "Thread1";   
                thread1.Start();
                Console.WriteLine("Main Thread is running");
                thread1.Resume();     
                Console.Read();
            }
    
            private static void TestMethod()
            {     
                Console.WriteLine("Thread: {0} has been suspended!", Thread.CurrentThread.Name);
                Thread.Sleep(1000);
    
                //将当前线程挂起
                Thread.CurrentThread.Suspend();          
                Console.WriteLine("Thread: {0} has been resumed!", Thread.CurrentThread.Name);
            }
        }
    复制代码

    当主线程跑(运行)的太快,做完自己的事情去唤醒thread1时,此时thread1还没有挂起而起唤醒thread1,此时就会出现异常了。并且上面使用的Suspend和Resume方法,编译器已经出现警告了,提示这两个方法已经过时, 所以在我们平时使用中应该尽量避免。

    1.2 Abort和 Interrupt方法

    Abort方法和Interrupt都是用来终止线程的,但是两者还是有区别的。

    1、他们抛出的异常不一样,Abort 方法抛出的异常是ThreadAbortException, Interrupt抛出的异常为ThreadInterruptedException

    2、调用interrupt方法的线程之后可以被唤醒,然而调用Abort方法的线程就直接被终止不能被唤醒的。

    下面一段代码是掩饰Abort方法的使用

    复制代码
    using System;
    using System.Threading;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Thread abortThread = new Thread(AbortMethod);
                abortThread.Name = "Abort Thread";
                abortThread.Start();
                Thread.Sleep(1000);
                try
                {
                    abortThread.Abort();     
                }
                catch 
                {
                    Console.WriteLine("{0} Exception happen in Main Thread", Thread.CurrentThread.Name);
                    Console.WriteLine("{0} Status is:{1} In Main Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
                }
                finally
                {
                    Console.WriteLine("{0} Status is:{1} In Main Thread ", abortThread.Name, abortThread.ThreadState);
                }
    
                abortThread.Join();
                Console.WriteLine("{0} Status is:{1} ", abortThread.Name, abortThread.ThreadState);
                Console.Read();
               
            }
    
            private static void AbortMethod()
            {
                try
                {
                    Thread.Sleep(5000);
                }
                catch(Exception e)
                {
                    Console.WriteLine(e.GetType().Name);
                    Console.WriteLine("{0} Exception happen In Abort Thread", Thread.CurrentThread.Name);
                    Console.WriteLine("{0} Status is:{1} In Abort Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
                }
                finally
                {
                    Console.WriteLine("{0} Status is:{1} In Abort Thread", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
                }
            }
        }
    复制代码

    运行结果:

    从运行结果可以看出,调用Abort方法的线程引发的异常类型为ThreadAbortException, 以及异常只会在 调用Abort方法的线程中发生,而不会在主线程中抛出,并且调用Abort方法后线程的状态不是立即改变为Aborted状态,而是从AbortRequested->Aborted。

    Interrupt方法:

    复制代码
    using System;
    using System.Threading;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            { Thread interruptThread = new Thread(AbortMethod);
                interruptThread.Name = "Interrupt Thread";
                interruptThread.Start();  
                interruptThread.Interrupt();     
               
                interruptThread.Join();
                Console.WriteLine("{0} Status is:{1} ", interruptThread.Name, interruptThread.ThreadState);
                Console.Read();     
            }
    
            private static void AbortMethod()
            {
                try
                {
                    Thread.Sleep(5000);
                }
                catch(Exception e)
                {
                    Console.WriteLine(e.GetType().Name);
                    Console.WriteLine("{0} Exception happen In Interrupt Thread", Thread.CurrentThread.Name);
                    Console.WriteLine("{0} Status is:{1} In Interrupt Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
                }
                finally
                {
                    Console.WriteLine("{0} Status is:{1} In Interrupt Thread", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
                }
    
            }
        }
    }
    复制代码

    运行结果:

    从结果中可以得到,调用Interrupt方法抛出的异常为:ThreadInterruptException, 以及当调用Interrupt方法后线程的状态应该是中断的, 但是从运行结果看此时的线程因为了Join,Sleep方法而唤醒了线程,为了进一步解释调用Interrupt方法的线程可以被唤醒, 我们可以在线程执行的方法中运用循环,如果线程可以唤醒,则输出结果中就一定会有循环的部分,然而调用Abort方法线程就直接终止,就不会有循环的部分,下面代码相信大家看后肯定会更加理解两个方法的区别的:

    复制代码
    using System;
    using System.Threading;
    
    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                Thread thread1 = new Thread(TestMethod);
                thread1.Start();
                Thread.Sleep(100);
    
                thread1.Interrupt();
                Thread.Sleep(3000);
                Console.WriteLine("after finnally block, the Thread1 status is:{0}", thread1.ThreadState);
                Console.Read();
            }
            private static void TestMethod()
            {
                
                for (int i = 0; i < 4; i++)
                {
                    try
                    {
                        Thread.Sleep(2000);
                        Console.WriteLine("Thread is Running");
                    }
                    catch (Exception e)
                    {
                        if (e != null)
                        {
                            Console.WriteLine("Exception {0} throw ", e.GetType().Name);
                        }
                    }
                    finally
                    {
                        Console.WriteLine("Current Thread status is:{0} ", Thread.CurrentThread.ThreadState);
                    }
                }
            }
        }
    }
    复制代码

    运行结果为:

    如果把上面的 thread1.Interrupt();改为 thread1.Abort(); 运行结果为:

    二、线程池基础

    首先,创建和销毁线程是一个要耗费大量时间的过程,另外,太多的线程也会浪费内存资源,所以通过Thread类来创建过多的线程反而有损于性能,为了改善这样的问题 ,.net中就引入了线程池。

    线程池形象的表示就是存放应用程序中使用的线程的一个集合(就是放线程的地方,这样线程都放在一个地方就好管理了)。CLR初始化时,线程池中是没有线程的,在内部, 线程池维护了一个操作请求队列,当应用程序想执行一个异步操作时,就调用一个方法,就将一个任务放到线程池的队列中,线程池中代码从队列中提取任务,将这个任务委派给一个线程池线程去执行,当线程池线程完成任务时,线程不会被销毁,而是返回到线程池中,等待响应另一个请求。由于线程不被销毁, 这样就可以避免因为创建线程所产生的性能损失。

    注意:通过线程池创建的线程默认为后台线程,优先级默认为Normal.

    三、通过线程池的工作者线程实现异步

    3.1 创建工作者线程的方法

    public static bool QueueUserWorkItem (WaitCallback callBack);

    public static bool QueueUserWorkItem(WaitCallback callback, Object state);

    这两个方法向线程池的队列添加一个工作项(work item)以及一个可选的状态数据。然后,这两个方法就会立即返回。

    工作项其实就是由callback参数标识的一个方法,该方法将由线程池线程执行。同时写的回调方法必须匹配System.Threading.WaitCallback委托类型,定义为:

    public delegate void WaitCallback(Object state);

    下面演示如何通过线程池线程来实现异步调用:

    复制代码
    using System;
    using System.Threading;
    
    namespace ThreadPoolUse
    {
        class Program
        {
            static void Main(string[] args)
            {
                // 设置线程池中处于活动的线程的最大数目
                // 设置线程池中工作者线程数量为1000,I/O线程数量为1000
                ThreadPool.SetMaxThreads(1000, 1000);
                Console.WriteLine("Main Thread: queue an asynchronous method");
                PrintMessage("Main Thread Start");
    
                // 把工作项添加到队列中,此时线程池会用工作者线程去执行回调方法
                ThreadPool.QueueUserWorkItem(asyncMethod);
                Console.Read();
            }
    
            // 方法必须匹配WaitCallback委托
            private static void asyncMethod(object state)
            {
                Thread.Sleep(1000);
                PrintMessage("Asynchoronous Method");
                Console.WriteLine("Asynchoronous thread has worked ");
            }
    
            // 打印线程池信息
            private static void PrintMessage(String data)
            {
                int workthreadnumber;
                int iothreadnumber;
    
                // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量
                // 获得的可用I/O线程数量给iothreadnumber变量
                ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
    
                Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
                    data,
                    Thread.CurrentThread.ManagedThreadId, 
                    Thread.CurrentThread.IsBackground.ToString(),
                    workthreadnumber.ToString(),
                    iothreadnumber.ToString());
            }
        }
    }
    复制代码

    运行结果:

    从结果中可以看出,线程池中的可用的工作者线程少了一个,用去执行回调方法了。

    ThreadPool.QueueUserWorkItem(WaitCallback callback,Object state) 方法可以把object对象作为参数传送到回调函数中,使用和ThreadPool.QueueUserWorkItem(WaitCallback callback)的使用和类似,这里就不列出了。

    3.2 协作式取消

    .net Framework提供了取消操作的模式, 这个模式是协作式的。为了取消一个操作,首先必须创建一个System.Threading.CancellationTokenSource对象。

    下面代码演示了协作式取消的使用,主要实现当用户在控制台敲下回车键后就停止数数方法。

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    
    namespace ConsoleApplication3
    {
        class Program
        {
            static void Main(string[] args)
            {
                ThreadPool.SetMaxThreads(1000, 1000);
                Console.WriteLine("Main thread run");    
                PrintMessage("Start");
                Run();
                Console.ReadKey();
            }
    
            private static void Run()
            {
                CancellationTokenSource cts = new CancellationTokenSource();
    
                // 这里用Lambda表达式的方式和使用委托的效果一样的,只是用了Lambda后可以少定义一个方法。
                // 这在这里就是让大家明白怎么lambda表达式如何由委托转变的
                ////ThreadPool.QueueUserWorkItem(o => Count(cts.Token, 1000));
    
                ThreadPool.QueueUserWorkItem(callback, cts.Token);
    
                Console.WriteLine("Press Enter key to cancel the operation\n");
                Console.ReadLine();
    
                // 传达取消请求
                cts.Cancel();
            }
            
            private static void callback(object state)
            {
                Thread.Sleep(1000);
                PrintMessage("Asynchoronous Method Start");
                CancellationToken token =(CancellationToken)state;    
                Count(token, 1000);
            }
    
            // 执行的操作,当受到取消请求时停止数数
            private static void Count(CancellationToken token,int countto)
            {
                for (int i = 0; i < countto; i++)
                {
                    if (token.IsCancellationRequested)
                    {
                        Console.WriteLine("Count is canceled");
                        break;
                    }
    
                    Console.WriteLine(i);
                    Thread.Sleep(300);
                }
                
                Console.WriteLine("Cout has done");       
            }
    
            // 打印线程池信息
            private static void PrintMessage(String data)
            {
                int workthreadnumber;
                int iothreadnumber;
    
                // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量
                // 获得的可用I/O线程数量给iothreadnumber变量
                ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
    
                Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
                    data,
                    Thread.CurrentThread.ManagedThreadId,
                    Thread.CurrentThread.IsBackground.ToString(),
                    workthreadnumber.ToString(),
                    iothreadnumber.ToString());
            }
        }
    }
    复制代码

    运行结果:

    四、使用委托实现异步

    通过调用ThreadPool的QueueUserWorkItem方法来来启动工作者线程非常方便,但委托WaitCallback指向的是带有一个参数的无返回值的方法,如果我们实际操作中需要有返回值,或者需要带有多个参数, 这时通过这样的方式就难以实现, 为了解决这样的问题,我们可以通过委托来建立工作这线程,

    下面代码演示了使用委托如何实现异步:

    复制代码
    using System;
    using System.Threading;
    
    namespace Delegate
    {
        class Program
        {
            // 使用委托的实现的方式是使用了异步变成模型APM(Asynchronous Programming Model)
            // 自定义委托
            private delegate string MyTestdelegate();
    
            static void Main(string[] args)
            {
                ThreadPool.SetMaxThreads(1000, 1000);
                PrintMessage("Main Thread Start");
    
                //实例化委托
                MyTestdelegate testdelegate = new MyTestdelegate(asyncMethod);
    
                // 异步调用委托
                IAsyncResult result = testdelegate.BeginInvoke(null, null);
    
                // 获取结果并打印出来
                string returndata = testdelegate.EndInvoke(result);
                Console.WriteLine(returndata);
    
                Console.ReadLine();
            }
            private static string asyncMethod()
            {
                Thread.Sleep(1000);
                PrintMessage("Asynchoronous Method");
                return "Method has completed";
            }
    
            // 打印线程池信息
            private static void PrintMessage(String data)
            {
                int workthreadnumber;
                int iothreadnumber;
    
                // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量
                // 获得的可用I/O线程数量给iothreadnumber变量
                ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
    
                Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
                    data,
                    Thread.CurrentThread.ManagedThreadId,
                    Thread.CurrentThread.IsBackground.ToString(),
                    workthreadnumber.ToString(),
                    iothreadnumber.ToString());
            }
        }
    }
    复制代码

    运行结果:

    五、任务

    同样 任务的引入也是为了解决通过ThreadPool.QueueUserWorkItem中限制的问题,

    下面代码演示通过任务来实现异步:

    5.1 使用任务来实现异步

    复制代码
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TaskUse
    {
        class Program
        {
            static void Main(string[] args)
            {
                ThreadPool.SetMaxThreads(1000, 1000);
                PrintMessage("Main Thread Start");
                // 调用构造函数创建Task对象,
                Task<int> task = new Task<int>(n => asyncMethod((int)n), 10);
    
                // 启动任务 
                task.Start();
                // 等待任务完成
                task.Wait();
                Console.WriteLine("The Method result is: "+task.Result);
    
                Console.ReadLine();
            }
    
            private static int asyncMethod(int n)
            {
                Thread.Sleep(1000);
                PrintMessage("Asynchoronous Method");
    
                int sum = 0;
                for (int i = 1; i < n; i++)
                {
                    // 如果n太大,使用checked使下面代码抛出异常
                    checked
                    {
                        sum += i;
                    }
                }
    
                return sum;
            }
    
            // 打印线程池信息
            private static void PrintMessage(String data)
            {
                int workthreadnumber;
                int iothreadnumber;
    
                // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量
                // 获得的可用I/O线程数量给iothreadnumber变量
                ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
    
                Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
                    data,
                    Thread.CurrentThread.ManagedThreadId,
                    Thread.CurrentThread.IsBackground.ToString(),
                    workthreadnumber.ToString(),
                    iothreadnumber.ToString());
            }
        }
    }
    复制代码


    运行结果:

    5.2 取消任务

    如果要取消任务, 同样可以使用一个CancellationTokenSource对象来取消一个Task.

    下面代码演示了如何来取消一个任务:

    复制代码
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TaskUse
    {
        class Program
        {
            static void Main(string[] args)
            {
                ThreadPool.SetMaxThreads(1000, 1000);
                PrintMessage("Main Thread Start");
                CancellationTokenSource cts = new CancellationTokenSource();
    
                // 调用构造函数创建Task对象,将一个CancellationToken传给Task构造器从而使Task和CancellationToken关联起来
                Task<int> task = new Task<int>(n => asyncMethod(cts.Token, (int)n), 10);
    
                // 启动任务 
                task.Start();
    
                // 延迟取消任务
                Thread.Sleep(3000);
    
                // 取消任务
                cts.Cancel();
                Console.WriteLine("The Method result is: " + task.Result);
                Console.ReadLine();
            }
    
            private static int asyncMethod(CancellationToken ct, int n)
            {
                Thread.Sleep(1000);
                PrintMessage("Asynchoronous Method");
    
                int sum = 0;
                try
                {
                    for (int i = 1; i < n; i++)
                    {
                        // 当CancellationTokenSource对象调用Cancel方法时,
                        // 就会引起OperationCanceledException异常
                        // 通过调用CancellationToken的ThrowIfCancellationRequested方法来定时检查操作是否已经取消,
                        // 这个方法和CancellationToken的IsCancellationRequested属性类似
                        ct.ThrowIfCancellationRequested();
                        Thread.Sleep(500);
                        // 如果n太大,使用checked使下面代码抛出异常
                        checked
                        {
                            sum += i;
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception is:" + e.GetType().Name);
                    Console.WriteLine("Operation is Canceled");
                }
    
                return sum;
            }
    
            // 打印线程池信息
            private static void PrintMessage(String data)
            {
                int workthreadnumber;
                int iothreadnumber;
    
                // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量
                // 获得的可用I/O线程数量给iothreadnumber变量
                ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
    
                Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
                    data,
                    Thread.CurrentThread.ManagedThreadId,
                    Thread.CurrentThread.IsBackground.ToString(),
                    workthreadnumber.ToString(),
                    iothreadnumber.ToString());
            }
        }
    }
    复制代码

    运行结果:

    5.3 任务工厂

    同样可以通过任务工厂TaskFactory类型来实现异步操作。

    复制代码
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TaskFactory
    {
        class Program
        {
            static void Main(string[] args)
            {
                ThreadPool.SetMaxThreads(1000, 1000);
                Task.Factory.StartNew(() => PrintMessage("Main Thread")); 
                Console.Read();
            }
            // 打印线程池信息
            private static void PrintMessage(String data)
            {
                int workthreadnumber;
                int iothreadnumber;
    
                // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量
                // 获得的可用I/O线程数量给iothreadnumber变量
                ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
    
                Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
                    data,
                    Thread.CurrentThread.ManagedThreadId,
                    Thread.CurrentThread.IsBackground.ToString(),
                    workthreadnumber.ToString(),
                    iothreadnumber.ToString());
            }
        }
    }
    复制代码

    运行结果:

    讲到这里CLR的工作者线程大致讲完了,希望也篇文章可以让大家对线程又有进一步的理解。在后面的一篇线程系列将谈谈CLR线程池的I/O线程。

  • 相关阅读:
    AtCoder Beginner Contest 167
    AtCoder Beginner Contest 166
    AtCoder Beginner Contest 165
    AtCoder Beginner Contest 164
    AtCoder Beginner Contest 163
    AtCoder Beginner Contest 162
    AtCoder Beginner Contest 161
    AtCoder Beginner Contest 160
    AtCoder Beginner Contest 159
    自定义Mybatis自动生成代码规则
  • 原文地址:https://www.cnblogs.com/ywsoftware/p/3077227.html
Copyright © 2011-2022 走看看