CLR via C#线程池与委托异步调用

来源:互联网 发布:jquery php cookie 编辑:程序博客网 时间:2024/05/22 05:18

CLR线程池

CLR线程池

一个应用程序最多只能有一个线程池。线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。

CLR线程池用于对.NET中线程的管理,其基本的工作方式为:

  1. 线程池的最开始是没有线程的,线程池有一个请求队列。
  2. 当应用程序请求一个线程的时候,CLR会调用某个方法,将请求放入线程池的请求队列中。
  3. 然后线程池会创建新的线程,将应用程序请求分配给新建的线程进行处理。
  4. 若线程完成应用的请求后,线程会返还至线程池中,等待新的任务请求。
  5. 若长时间没有新的请求,则线程会自动终结。

线程池中线程的分类:线程池中默认分为工作者线程I/O线程
其中工作者线程是平时用的最多的,通常用于计算密集的任务。而I/O线程则是完成向磁盘和数据库读写数据的操作。

线程池的最大值一般默认为1000。当大于此数目的请求时,将保持排队状态,直到线程池里有线程可用。

何时使用线程池?

使用线程池条件

ThreadPool 类

方法 说明 GetMaxThreads 同时运行的最多线程数,所有大于此数目的请求将保持排队状态,直到线程池线程变为可用 GetMinThreads 在没有请求时,维护的空闲线程数 SetMaxThreads 设置可以同时处于活动状态的线程池的请求数目 SetMinThreads 设置线程池在新请求预测中维护的空闲线程数 QueueUserWorkItem 启动线程池里得一个线程 GetAvailableThreads 剩余可用的空闲线程数

除非有特殊需要,否则不要修改线程池中的最大线程数和空闲线程默认值

static void Main(string[] args)        {            int Workernum = 0;            int IOnum = 0;            //前面工作者线程,后面是I/O线程            ThreadPool.GetMaxThreads(out Workernum , out IOnum );            Console.WriteLine(Workernum + "   " + IOnum);            //获取剩余可用的空闲线程数            ThreadPool.GetAvailableThreads(out Workernum , out IOnum );            Console.WriteLine( Workernum + "   " + IOnum);            Console.ReadKey();        }

通过QueueUserWorkItem(new WaitCallback())启动工作者线程:

static void Main(string[] args)        {              Person p = new Person("工人1");                      //启动工作者线程            ThreadPool.QueueUserWorkItem(new WaitCallback(WorkerFunc), p);            Console.ReadKey();        }        //注意异步调用的方法只能接受object类型        static void WorkerFunc(object arg)        {               Person p= arg as Person;            Console.WriteLine( p.Name + "报道");        }

由于WaitCallback委托指向的必须是一个带有object参数的无返回值方法。所以这个方法启动的工作者线程仅仅局限于带单个参数和无返回值的情况。

委托异步调用线程

BeginInvoke与EndInvoke委托异步调用线程

class Program    {        //定义返回值为string的委托        delegate string MyDelegate(string name,int age);        static void Main(string[] args)        {            //创建委托对象,将GetString方法作为参数            MyDelegate myDelegate = new MyDelegate(GetString);            //异步调用委托,除最后两个参数外,前面的参数都可以传进去            IAsyncResult result = myDelegate.BeginInvoke("小刘",18, null, null);            Console.WriteLine("主线程继续执行");            //AsyncResult.IsCompleted判断异步调用是否结束            while (!result.IsCompleted)            {                Console.Write("*");                Thread.Sleep(100);            }            //一旦调用了EndInvoke,即使结果还没来得及返回,主线程也要阻塞等待            string data = myDelegate.EndInvoke(result);            Console.WriteLine(data);            Console.ReadKey();        }        static string GetString(string name, int age)        {                        Thread.Sleep(2000);            return string.Format("我是{0},今年{1}岁",name,age);        }    }

BeginInvoke创建的线程都是后台线程,如果不调用EndInvoke,那么主线程执行完后将会关闭所有后台线程。但是调用EndInvoke的时候,主线程会被阻塞,看起来就像程序卡死一样,感觉很shit~,所以通过AsyncResult.IsCompleted判断异步调用是否结束会提高用户体验。

除了IsCompleted属性外,还可以使用AsyncWaitHandle以下3个方法实现轮询判断效果:
WaitOne:判断单个异步线程是否完成
WaitAny:判断异步线程是否已完成指定数量
WaitAll:判断是否所有的异步线程已完成

  //每隔0.2s判断单个异步是否完成  while (!result.AsyncWaitHandle.WaitOne(200))  {      Console.WriteLine("异步线程没完,主线程继续干活!");  }  //每隔0.2s判断是否完成了指定数量的异步线程  WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };  while (WaitHandle.WaitAny(waitHandleList, 200) > 0)  {      Console.WriteLine("异步线程完成数未大于0,主线程继续干活!");  }  //每隔0.2s判断是否完成全部异步线程  WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };  while (!WaitHandle.WaitAll(waitHandleList, 200))  {      Console.WriteLine("异步线程未全部完成,主线程继续干其他事!");  }

使用轮询方式来检测异步方法的状态非常麻烦,而且影响了主线程,效率不高。这时就用到了IAsyncResult对象的倒数第二个参数,回调函数。

class Program    {        delegate string MyDelegate(string name, int age);        static void Main(string[] args)        {            //建立委托            MyDelegate myDelegate = new MyDelegate(GetString);            Person p = new Person("关羽");            //倒数第二个参数,委托中绑定了完成后的回调方法            //最后一个参数是往回调方法里传的任意类型参数            IAsyncResult result1 = myDelegate.BeginInvoke("小刘",18, new AsyncCallback(Completed), p);            Console.WriteLine("主线程可以继续工作而不需要等待");            Thread.Sleep(5000);            Console.ReadKey();        }        static string GetString(string name, int age)        {            Thread.CurrentThread.Name = "异步线程GetString";            //注意,如果不设置为前台线程,则主线程完成后就直接卸载程序了            //Thread.CurrentThread.IsBackground = false;            Thread.Sleep(2000);            return string.Format("我是{0},今年{1}岁!", name, age);        }        //供异步线程完成回调的方法        static void Completed(IAsyncResult result)        {            //获取委托对象,调用EndInvoke方法获取运行结果            AsyncResult _result = result;            MyDelegate myDelegaate = _result.AsyncDelegate as myDelegaate ;            //获得结果            string data = myDelegaate.EndInvoke(_result);            Console.WriteLine(data);            //获得传入的参数            Person p = result.AsyncState as Person;            //异步线程执行完毕            Console.WriteLine("传入的参数Name是" + p.Name);            Console.WriteLine("回调函数也是由" + Thread.CurrentThread.Name + "调用的!");        }    }

注意
回调函数是在辅助线程中执行的,所以不会影响主线程的运行。
线程池的线程默认是后台线程。但是如果主线程比辅助线程优先完成,那么程序已经卸载,回调函数未必会执行。

文章参考:

https://msdn.microsoft.com/zh-cn/library/system.threading.threadpool_methods(v=vs.80).aspx

http://www.cnblogs.com/kissdodog/archive/2013/03/28/2986026.html

http://www.cnblogs.com/nokiaguy/archive/2008/07/13/1241817.html

合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。加油!!

原创粉丝点击