博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[.Net 多线程处理系列]专题二:线程池中的工作者线程
阅读量:5092 次
发布时间:2019-06-13

本文共 19279 字,大约阅读时间需要 64 分钟。

目录:

一、上节补充

二、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
task = new Task
(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
task = new Task
(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线程。

转载于:https://www.cnblogs.com/ShaYeBlog/archive/2012/09/26/2704291.html

你可能感兴趣的文章
【2.3】初始Django Shell
查看>>
Linux(Centos)之安装Redis及注意事项
查看>>
bzoj 1010: [HNOI2008]玩具装箱toy
查看>>
Kotlin动态图
查看>>
从零开始系列之vue全家桶(1)安装前期准备nodejs+cnpm+webpack+vue-cli+vue-router
查看>>
ASP.NET缓存 Cache之数据缓存
查看>>
bzoj3529: [Sdoi2014]数表
查看>>
SSH三大框架 整合必备jar包
查看>>
什么是电子商务?电子商务面临的几个关键问题及解决办法?电子商务的核心是什么?B2C电子商务运营的核心是什么 ?...
查看>>
Jsp抓取页面内容
查看>>
AJAX与servlet的组合,最原始的
查看>>
大三上学期软件工程作业之点餐系统(网页版)的一些心得
查看>>
MySQL 数据表修复及数据恢复
查看>>
可选参数的函数还可以这样设计!
查看>>
走高端树品牌 IT大佬竞相“归田”
查看>>
大型网站应用之海量数据和高并发解决方案总结一二
查看>>
[BZOJ4518][SDOI2016]征途(斜率优化DP)
查看>>
Android recycleView的研究和探讨
查看>>
HDU1024 Max Sum Plus Plus 【DP】
查看>>
[你必须知道的.NET]第二十一回:认识全面的null
查看>>