欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#线程学习笔记二:线程池中的工作者线程

程序员文章站 2022-06-20 08:51:52
本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/ThreadPool.html,记录一下学习过程以备后续查用。 一、线程池基础 首先,创建和销毁线程是一个要耗费大量时间的过程,其次,太多的线程也会浪费内存资源,所以通过Thread类来创 ......

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/threadpool.html,记录一下学习过程以备后续查用。

    一、线程池基础

    首先,创建和销毁线程是一个要耗费大量时间的过程,其次,太多的线程也会浪费内存资源,所以通过thread类来创建过多的线程反而有损于性能。为了改善这样

的问题 ,.net中就引入了线程池。

    线程池形象的表示就是存放应用程序中使用的线程的一个集合(就是放线程的地方,这样线程都放在一个地方就好管理了)。

    clr初始化时,线程池中是没有线程的,在内部, 线程池维护了一个操作请求队列。当应用程序想执行一个异步操作时,就调用一个方法,将一个任务放到线程池

的队列中,线程池代码从队列中提取任务,将这个任务委派给一个线程池线程去执行,当线程池线程完成任务时,线程不会被销毁,而是返回到线程池中,等待响应另

一个请求。由于线程不被销毁, 这样就可以避免因为创建线程所产生的性能损失。

    msdn表述:

    “线程池经常用在服务器应用程序中,每一个新进来的需求被分配给一个线程池中的线程,这样该需求能被异步的执行,没有阻碍主线程或推迟后继需求的处理。”

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

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

    2.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);

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

    class program
    {
        static void main(string[] args)
        {
            #region 通过线程池的工作者线程实现异步
            //设置线程池中工作者线程最大数量为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();
            #endregion
        }

        /// <summary>
        /// 打印线程池信息
        /// </summary>
        /// <param name="data"></param>
        private static void printmessage(string data)
        {
            // 获得线程池中可用的工作者线程数量及i/o线程数量
            threadpool.getavailablethreads(out int workthreadnumber, out int 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());
        }

        /// <summary>
        /// 异步方法:必须匹配waitcallback委托
        /// </summary>
        /// <param name="state"></param>
        private static void asyncmethod(object state)
        {
            thread.sleep(1000);
            printmessage("asynchoronous method.");
            console.writeline("asynchoronous thread has worked.");
        }
    }

    运行结果如下:

C#线程学习笔记二:线程池中的工作者线程

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

    threadpool.queueuserworkitem(waitcallback callback,object state) 方法可以把object对象作为参数传送到回调函数中,使用方法与

threadpool.queueuserworkitem(waitcallback callback)类似,这里就不列出了。

    2.2 协作式取消

    .net framework提供了取消操作的模式, 这个模式是协作式的。为了取消一个操作,首先必须创建一个system.threading.cancellationtokensource对象。

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

    class program
    {
        static void main(string[] args)
        {
            #region 协作式取消
            threadpool.setmaxthreads(1000, 1000);
            console.writeline("main thread run.");
            printmessage("start");
            run();
            console.readkey();
            #endregion
        }

        /// <summary>
        /// 打印线程池信息
        /// </summary>
        /// <param name="data"></param>
        private static void printmessage(string data)
        {
            //获得线程池中可用的工作者线程数量及i/o线程数量
            threadpool.getavailablethreads(out int workthreadnumber, out int 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());
        }

        /// <summary>
        /// 运行工作者线程(包含协作式取消)
        /// </summary>
        private static void run()
        {
            cancellationtokensource cts = new cancellationtokensource();

            //这里是用lambda表达式的写法,效果一样。
            //threadpool.queueuserworkitem(obj => count(cts.token, 1000));

            threadpool.queueuserworkitem(callback, cts.token);
            console.writeline("press enter key to cancel the operation.\n");
            console.readline();
            //传达取消请求
            cts.cancel();
        }

        /// <summary>
        /// 回调函数
        /// </summary>
        /// <param name="state"></param>
        private static void callback(object state)
        {
            thread.sleep(1000);
            printmessage("asynchoronous method start.");
            cancellationtoken token = (cancellationtoken)state;
            count(token, 1000);
        }

        /// <summary>
        /// 数数
        /// </summary>
        /// <param name="token"></param>
        /// <param name="countto"></param>
        private static void count(cancellationtoken token, int countto)
        {
            for (int i = 1; i <= countto; i++)
            {
                if (token.iscancellationrequested)
                {
                    console.writeline("count is canceled.");
                    break;
                }

                console.writeline(i);
                thread.sleep(300);
            }
            console.writeline("count has done.");
        }
    }

    运行结果如下:

C#线程学习笔记二:线程池中的工作者线程

    三、使用委托实现异步

    涉及术语解释--异步编程模型:

    apm 异步编程模型(asynchronous programming model)

    eap 基于事件的异步编程模式(event-based asynchronous pattern)

    tap 基于任务的异步编程模式(task-based asynchronous pattern)

    通过调用threadpool的queueuserworkitem方法来来启动工作者线程非常方便,但委托waitcallback指向的是带有一个参数的无返回值的方法。如果我们实际操作中

需要有返回值,或者需要带有多个参数, 这时通过这样的方式就难以实现了。 为了解决这样的问题,我们可以通过委托来建立工作这线程。

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

    class program
    {
        //使用委托实现异步,是使用了异步编程模型apm。
        private delegate string threaddelegate();

        static void main(string[] args)
        {
            #region 使用委托实现异步
            threadpool.setmaxthreads(1000, 1000);
            printmessage("main thread start.");

            //实例化委托
            threaddelegate threaddelegate = new threaddelegate(asyncmethod);
            //异步调用委托
            iasyncresult result = threaddelegate.begininvoke(null, null);
            //获取结果并打印
            string returndata = threaddelegate.endinvoke(result);
            console.writeline(returndata);
            console.readline();
            #endregion
        }

        /// <summary>
        /// 异步方法
        /// </summary>
        /// <returns></returns>
        private static string asyncmethod()
        {
            thread.sleep(1000);
            printmessage("asynchoronous method.");
            return "method has completed.";
        }
    }

    运行结果如下:

C#线程学习笔记二:线程池中的工作者线程

    四、任务

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

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

    4.1 使用任务来实现异步

    class program
    {
        static void main(string[] args)
        {
            #region 使用任务实现异步
            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();
            #endregion
        }

        /// <summary>
        /// 异步方法
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        private static int asyncmethod(int n)
        {
            thread.sleep(1000);
            printmessage("asynchoronous method.");

            int sum = 0;
            for (int i = 1; i < n; i++)
            {
                //运算溢出检查
                checked
                {
                    sum += i;
                }
            }

            return sum;
        }
    }

    运行结果如下:

C#线程学习笔记二:线程池中的工作者线程

    4.2 取消任务

    如果要取消任务, 同样也可以cancellationtokensource对象来取消。

    下面代码演示取消一个任务:

    class program
    {
        static void main(string[] args)
        {
            #region 取消任务
            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();
            #endregion
        }

        /// <summary>
        /// 异步方法
        /// </summary>
        /// <param name="ct"></param>
        /// <param name="n"></param>
        /// <returns></returns>
        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);
                    //运算溢出检查
                    checked
                    {
                        sum += i;
                    }
                }
            }
            catch (exception e)
            {
                console.writeline("exception is:" + e.gettype().name);
                console.writeline("operation is canceled.");
            }

            return sum;
        }
    }

    运算结果如下:

C#线程学习笔记二:线程池中的工作者线程

    4.3 任务工厂

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

 

    class program
    {
        static void main(string[] args)
        {
            #region 使用任务工厂实现异步
            threadpool.setmaxthreads(1000, 1000);
            task.factory.startnew(() => printmessage("main thread."));
            console.read();
            #endregion
        }

        /// <summary>
        /// 打印线程池信息
        /// </summary>
        /// <param name="data"></param>
        private static void printmessage(string data)
        {
            //获得线程池中可用的工作者线程数量及i/o线程数量
            threadpool.getavailablethreads(out int workthreadnumber, out int 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());
        }
    }

    运行结果如下:

C#线程学习笔记二:线程池中的工作者线程