zoukankan      html  css  js  c++  java
  • C#中的线程池使用(二)

    线程池是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。每个进程只有一个线程池对象。

    下面说一下线程池中的异常,在线程池中未处理的异常将终止进程。以下为此规则的三种例外情况:

    (1)由于调用了Abort,线程池线程中将引发ThreadAbortException异常(在对Abort方法进行调用时引发的异常)。

    (2)由于正在卸载应用程序域,线程池线程中将引发AppDomainUnloadedException异常(在尝试访问已卸载的应用程序域时引发的异常)。

    (3)公共语言运行库或宿主进程将终止线程。

    如果公共语言运行库所创建的线程中未处理这些异常中的任何一个,则异常将终止线程,但公共语言运行库不允许该异常继续下去。

    如果在主线程或从非托管代码进入运行库的线程中未处理这些异常,则它们将正常继续,并导致应用程序终止。

    注意在 .NET Framework 1.0 和 1.1 版中,公共语言运行库将捕获线程池中的未处理异常,而不出现任何提示。这可能会破坏应用程序状态,并最终导致应用程序挂起,将很难进行调试。

    使用线程池的方式主要有4种,下面分别对其进行介绍。

    1.ThreadPool类的QueueUserWorkItem方法

    在使用线程池时,可以从托管代码中调用ThreadPool类的QueueUserWorkItem方法,或从非托管代码中调用CorQueueUserWorkItem方法,并用线程池线程要执行的回调方法WaitCallback执行线程池。

    QueueUserWorkItem方法将方法排入队列以便执行(并指定包含该方法所用数据的对象,用state参数来实现)。此方法在有线程池线程变得可用时执行。该方法有两个语法形式,其语法如下:

    public static bool QueueUserWorkItem(WaitCallback callBack)
    public static bool QueueUserWorkItem(WaitCallback callBack,Object state)

    参数说明:

    callBack:WaitCallback类型,它表示要执行的方法。

    State:Object类型,包含方法所用数据的对象。

    返回值:Boolean类型,如果此方法成功排队,则为true;如果无法将该工作项排队,则引发OutOfMemoryException异常。 

      示例   线程池的应用

    本示例用线程池顺序执行两个方法。代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    namespace ThreadPoolApple
    {
        class Program
        {
            public void thread1(Object obj)//定义方法thread1
            {
                for (int i = 0; i <= 3; i++)//输出0~3的值
                {
                    Console.Write(i.ToString());
                }
                Console.WriteLine();//换行
            }
            public void thread2(Object obj)//定义方法thread2
            {
                for (int i = 4; i <= 6; i++)//输出4~6的值
                {
                    Console.Write(i.ToString() + obj.ToString());//值后面加货币符号
                }
                Console.WriteLine();//换行
            }
            static void Main(string[] args)
            {
                string Ostr = "";//用字符串记录货币符号
                Program prog = new Program();//实例化Program类
                for (int i = 0; i <= 3; i++)
                {
                    //用线程池执行无参数方法
                    ThreadPool.QueueUserWorkItem(new WaitCallback(prog.thread1));
                    //用线程池执行有参数方法
                    ThreadPool.QueueUserWorkItem(new WaitCallback(prog.thread2),Ostr);
                }
                Console.ReadLine();
            }
        }
    }

    运行结果如下图所示。

     

    图  线程池的应用结果

     注意:在ThreadPool类中QueueUserWorkItem是一个静态方法,因此可以由ThreadPool类直接用。

    2.ThreadPool类的UnsafeQueueUserWorkItem方法

    该方法注册一个等待WaitHandle的委托。与QueueUserWorkItem方法不同,UnsafeQueueUserWorkItem 不会将调用堆栈传播到辅助线程。这使得代码可以失去调用堆栈,从而提升它的安全特权。语法如下:

    [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.NoFlags|SecurityPermissionFlag.ControlEvidence|SecurityPermissionFlag.ControlPolicy)]
    public static bool UnsafeQueueUserWorkItem(WaitCallback callBack,Object state)

    参数说明:

    callBack :System.Threading.WaitCallback类型,一个WaitCallback,表示当线程池中的线程选择工作项时调用的委托。

    state:Object类型,在接受线程池服务时传递给委托的对象。

    返回值:Boolean类型,如果方法成功,则为true;如果无法将该工作项排队,则引发OutOfMemoryException。

    在使用 UnsafeQueueUserWorkItem方法时,可能会无意中打开一个安全漏洞。代码访问安全性的权限检查基于所有调用方对堆栈的权限进行。如果使用 UnsafeQueueUserWorkItem 将工作排在某个线程池线程上,则该线程池线程的堆栈将不会具有实际调用方的上下文。恶意代码可能会利用这一点避开权限检查。

      示例     线程池的应用

    本示例主要讲解一下如何用UnsafeQueueUserWorkItem方法对线程池进行操作。代码如下:

    namespace _01_05
    {
        class Program
        {
            static void Main(string[] args)
            {
                //注册一个等待WaitHandle的委托
                ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(ThreadProc), "First task");
                Console.WriteLine("将主线程挂起");
                Thread.Sleep(1000);//挂走主线程
                Console.WriteLine("执行主线程");
                Console.ReadLine();
            }
            //自定义方法,主要用于线程池的调用方法
            public static void ThreadProc(object state)
            {
                Console.WriteLine("执行线程池   "+state.ToString());
            }
        }
    }

    运行结果如下图所示。

     

    与QueueUserWorkItem方法不同,UnsafeQueueUserWorkItem不会将调用堆栈传播到辅助线程。这使得代码可以失去调用堆栈,从而提升它的安全特权。

    3.ThreadPool类的RegisterWaitForSingleObject方法

    可以使用 ThreadPool类的RegisterWaitForSingleObject方法注册正在等待WaitHandle的委托。该方法一共有4个语法形式,下面分别对其进行介绍。

    l  语法1

    注册一个等待WaitHandle的委托,并指定一个32位有符号整数来表示超时值(以毫秒为单位)。其语法如下:

    public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,int millisecondsTimeOutInterval,bool executeOnlyOnce)

    waitObject:WaitHandle类型,要注册的WaitHandle。使用WaitHandle而非Mutex。

    CallBack:WaitOrTimerCallback类型,waitObject参数终止时调用的WaitOrTimerCallback委托。

    State:Object类型,传递给委托的对象。

    MillisecondsTimeOutInterval:Int32类型,以毫秒为单位的超时。如果millisecondsTimeOutInterval参数为0(零),函数将测试对象的状态并立即返回。如果millisecondsTimeOutInterval为-1,则函数的超时间隔永远不过期。

    ExecuteOnlyOnce:Boolean类型,如果为true,表示在调用了委托后,线程将不再在waitObject 参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。

    返回值:System.Threading.RegisteredWaitHandle类型,封装本机句柄的 RegisteredWaitHandle。

    l  语法2

    注册一个等待WaitHandle的委托,并指定一个64位有符号整数来表示超时值(以毫秒为单位)。其语法如下:

    public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,long millisecondsTimeOutInterval,bool executeOnlyOnce)

    参数说明:

    waitObject:WaitHandle类型,要注册的WaitHandle。使用WaitHandle而非Mutex。

    CallBack:WaitOrTimerCallback类型,waitObject参数终止时调用的WaitOrTimerCallback委托。

    State:Object类型,传递给委托的对象。

    millisecondsTimeOutInterval:Int64类型,以毫秒为单位的超时。如果millisecondsTimeOutInterval参数为0(零),函数将测试对象的状态并立即返回。如果millisecondsTimeOutInterval为-1,则函数的超时间隔永远不过期。

    ExecuteOnlyOnce:Boolean类型,如果为true,表示在调用了委托后,线程将不再在waitObject 参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。

    返回值:System.Threading.RegisteredWaitHandle类型,封装本机句柄的 RegisteredWaitHandle。

    l  语法3

    注册一个等待WaitHandle的委托,并指定一个TimeSpan值来表示超时时间。其语法如下:

    public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,TimeSpan timeout,bool xecuteOnlyOnce)

    参数说明:

    waitObject:WaitHandle类型,要注册的WaitHandle。使用WaitHandle而非Mutex。

    CallBack:WaitOrTimerCallback类型,waitObject参数终止时调用的WaitOrTimerCallback委托。

    State:Object类型,传递给委托的对象。

    Timeout:TimeSpan类型,TimeSpan表示的超时时间。如果timeout为0(零),则函数将测试对象的状态并立即返回。如果timeout为-1,则函数的超时间隔永远不过期。

    ExecuteOnlyOnce:Boolean类型,如果为true,表示在调用了委托后,线程将不再在waitObject 参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。

    返回值:System.Threading.RegisteredWaitHandle类型,封装本机句柄的 RegisteredWaitHandle。

    l  语法4

    指定表示超时(以毫秒为单位)的32位无符号整数,注册一个委托等待WaitHandle。其语法如下:

    [CLSCompliantAttribute(false)]
    public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,uint millisecondsTimeOutInterval,bool executeOnlyOnce)

    参数说明:

    waitObject:WaitHandle类型,要注册的WaitHandle。使用WaitHandle而非Mutex。

    CallBack:WaitOrTimerCallback类型,waitObject参数终止时调用的WaitOrTimerCallback委托。

    State:Object类型,传递给委托的对象。

    MillisecondsTimeOutInterval:UInt32类型,以毫秒为单位的超时。如果millisecondsTimeOutInterval参数为0(零),函数将测试对象的状态并立即返回。如果millisecondsTimeOutInterval为-1,则函数的超时间隔永远不过期。

    ExecuteOnlyOnce:Boolean类型,如果为true,表示在调用了委托后,线程将不再在waitObject参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。

    返回值:System.Threading.RegisteredWaitHandle类型,封装本机句柄的 RegisteredWaitHandle。

    注意:在以上的4个语法中,对waitObject应用Mutex不会导致回调互斥,因为它是基于Win32 API使用默认的WT_EXECUTEDEFAULT标志,所以每次回调都在单独的线程池线程上调度。因此,请尽可能的不要使用Mutex,应该使用最大计数为1的Semaphore。

    RegisterWaitForSingleObject方法将指定的委托排队到线程池。当发生以下两种情况时,辅助线程将执行委托:

    l  指定对象处于终止状态。

    l  超时间隔已过期。

    RegisterWaitForSingleObject方法检查指定对象的WaitHandle的当前状态。如果对象状态为非终止状态,则此方法将注册一个等待操作,该等待操作由线程池中的一个线程来执行。当对象状态变为终止或超时间隔已过期时,委托由辅助线程执行。如果 timeOutInterval参数不为0(零),并且executeOnlyOnce参数为false,则每当事件收到信号或超时间隔过期时都会重置计时器。若要取消等待操作,请调用RegisteredWaitHandle.Unregister方法。

    等待线程使用Win32的WaitForMultipleObjects函数来监视已注册的等待操作。因此,如果必须在对RegisterWaitForSingleObject的多次调用中使用相同的本机操作系统句柄,则必须使用Win32的DuplicateHandle函数重复该句柄。这里要注意的是,不应为传递到RegisterWaitForSingleObject的事件对象发出脉冲,这是因为等待线程在重置前可能不会检测到该事件已终止。

    返回前,函数将修改某些类型的同步对象的状态。修改仅发生在其终止状态满足等待条件的对象上。例如,信号量计数减少一。

    下面通过一个简单的例子,来说明一下如何使用RegisterWaitForSingleObject方法对线程池进行相应的操作。代码如下:

      示例    线程池的应用

    本示例通过注册正在等待的WaitHandle委托,对线程池进行操作。代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    namespace _01_04
    {
        class Program
        {
            public class TaskInfo//定义一个类
            {
                public RegisteredWaitHandle Handle = null;//定义一个句柄变量
                public string OtherInfo = "default";//定义一个字符串
            }
            static void Main(string[] args)
            {
                AutoResetEvent ev = new AutoResetEvent(false);//实例化等待线程已发生的事件,将初始状态设置为非终止
                TaskInfo ti = new TaskInfo();//实例化类
                ti.OtherInfo = "First task";//记录文本信息
                ti.Handle = ThreadPool.RegisterWaitForSingleObject(
                    ev,//要进行注册的WaitHandle类型
                    new WaitOrTimerCallback(WaitProc),//当WaitHandle超时或终止时要调用的方法
                    ti,//传递给委托对象的值
                    1000,//设置超时间隔
                    false//表示每次完成等待操作后都重置计时器,直到注销等待
                );//注册一个等待的委托
                Thread.Sleep(3100);//将主线程挂起
                Console.WriteLine("执行主线程");
                ev.Set();//将WaitHandle设置为终止
                Console.WriteLine("将等待线程已发生的事件设置为终止");
                Console.ReadLine();
            }
            //state:一个对象,包含回调方法在每次执行时要使用的信息
            //timedOut:如果 WaitHandle 超时,则为 true;如果其终止,则为 false。
            public static void WaitProc(object state, bool timedOut)
            {
                TaskInfo ti = (TaskInfo)state;
                if (!timedOut)//当WaitHandle为终止时
                {
                    if (ti.Handle != null)//如果委托对象的句柄不为空
                        ti.Handle.Unregister(null);//取消RegisterWaitForSingleObject方法所发出的已注册等待操作
                }
                string StrTime = timedOut ? " WaitHandle 超时" : "终止";
                Console.WriteLine("当前执行所使用的信息:" + state.ToString() + "" + StrTime + ";  传递给委托的对象:" + ti.OtherInfo);
    
            }
        }
    }

    运行结果如下图所示。

     

    图  RegisterWaitForSingleObject方法的应用

    在上图可以看出,在上例并没有使用for等循环语句执行线程池(ThreadPool)中的RegisterWaitForSingleObject方法,但它却执行了3次,这是为什么呢?其主要原因是将RegisterWaitForSingleObject方法中的ExecuteOnlyOnce参数设置为false(如果将其设置为true,调用了委托后,线程将不在waitObject参数上等待,也就是只执行一次),它表示每次完成等待操作后都重置计时器,直到取消由RegisterWaitForSingleObject方法发出的已经注册等待的操作,主要是用State参数来实现的,为了可以在该参数中记录所传递的委托对象,事先必须要定义一个类,在类中定义两个全局变量,用于记录委托的信息,以及本机的句柄。其类的定义如下:

    public class TaskInfo//定义一个类
    {
        public RegisteredWaitHandle Handle = null;//定义一个句柄变量
        public string OtherInfo = "default";//定义一个字符串
    }

    然后通过自定义类的Handle变量记录本机句柄,用OtherInfo变量记录委托信息,如果想要取消RegisterWaitForSingleObject方法所发出的已注册的等待操作,可以用Handle变量的Unregister(null)方法取消注册。

    为了读者能更好的定义RegisterWaitForSingleObject方法所执行的自定义事件,下面对RegisterWaitForSingleObject方法中CallBack参数所调用的方法进行说明,CallBack参数是WaitHandle类型的,主要是当WaitHandle超时或终止时所调用的方法,其语法结构为:

    [ComVisibleAttribute(true)]
    public delegate void WaitOrTimerCallback(Object state,bool timedOut)

    参数说明:

    state:Object类型,一个对象,包含回调方法在每次执行时要使用的信息。

    TimedOut:Boolean类型,如果WaitHandle超时,则为true;如果其终止,则为false。

    通过以上语法,用户可自定义一个方法,用于线程池的调用,代码如下:

    public static void WaitProc(object state, bool timedOut)
    {
    
    }
  • 相关阅读:
    NoHttp开源Android网络框架1.0.0之架构分析
    3种浏览器性能測试
    自己定义控件-画板,橡皮擦,刮刮乐
    android优化 清除无效代码 UCDetector
    iOS推送 (百度推送)
    C#中的协变OUT和逆变
    使用反射构造对象实例并动态调用方法
    用反射获取构造函数带参数的实例对象
    自己实现一个IOC(控制翻转,DI依赖注入)容器
    func 和action 委托的使用
  • 原文地址:https://www.cnblogs.com/DonetRen/p/10172047.html
Copyright © 2011-2022 走看看