多线程编程之线程池

1. 概述

1.1 关于线程池的理解

        ①线程池中的线程都是后台线程

        ②创建线程的开销是巨大的,因此,非必要情况下,应谨慎创建线程,以此减少线程的创建和销毁。一般情况下,应在线程池中选择线程来执行短暂的异步操作

        ③通过System.Threading.ThreadPool类型可以使用线程池

        ④线程池是受.NET通用语言运行时(Common Language Runtime,简称CLR)管理的。这意味着每个CLR都有一个线程池实例

        ⑤线程池创建线程的数量是有限制的,若线程执行较长时间的操作,就回导致线程池放入新的操作时,线程池会不断创建新的线程,当线程池达到一定数量后,放入线程池的操作在无空闲线程时,只能在队列中等待直到线程池中的工作线程有能力来执行。当停止向线程池中放置新操作时,线程池最终会删除一定时间后过期的不再使用的线程。

        ⑥ASP.NET基础设施使用自己的线程池,如果在线程池中浪费所有的工作者线程,Web服务器将不能够服务新的请求。在ASP.NET中只推荐使用输入/输出密集型的异步操作,因为其使用了一个不同的方式,叫做I/O线程。

1.2 使用线程池的作用

        ①线程池中的线程一般用于处理运行时间短的操作

        ②使用线程池可以减少并行度耗费及节省操作系统资源

        ③实现批量处理操作

2. 在线程池中调用委托

using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;

namespace Chapter3.Recipe1
{
	class Program
	{
		static void Main(string[] args)
		{
			int threadId = 0;

			RunOnThreadPool poolDelegate = Test;

			var t = new Thread(() => Test(out threadId));
			t.Start();
			t.Join();

			WriteLine($"Thread id: {threadId}");

            //BeginInvoke方法接受一个回调函数。该回调函数在异步操作完成后会被调用,
			//并且一个用户自定义的状态会传给该回调函数【该状态通常用于区分异步调用】。
            IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "a delegate asynchronous call");
			r.AsyncWaitHandle.WaitOne();

            //EndInvoke方法会等待异步操作完成
            //当操作完成后,传递给BeginInvoke方法的回调函数将被放置到线程池中,确切地说是一个工作者线程中
            string result = poolDelegate.EndInvoke(out threadId, r);
			
			WriteLine($"Thread pool worker thread id: {threadId}");
			WriteLine(result);
            //如果注释掉Thread.Sleep方法调用,
			//回调函数将不会被执行。这是因为当主线程完成后,所有的后台线程会被停止,包括该回调函数
            Sleep(TimeSpan.FromSeconds(2));

            //使用BeginOperationName/EndOperationName方法和.NET中的IAsyncResult对象等方式被称为异步编程模型(或APM模式),
			//这样的方法对称为异步方法

			Console.ReadKey();
        }
        //声明一个委托
        private delegate string RunOnThreadPool(out int threadId);

		/// <summary>
		/// 定义回调函数
		/// </summary>
		/// <param name="ar"></param>
		private static void Callback(IAsyncResult ar)
		{
			WriteLine("Starting a callback...");
			WriteLine($"State passed to a callbak: {ar.AsyncState}");
			WriteLine($"Is thread pool thread: {CurrentThread.IsThreadPoolThread}");
			WriteLine($"Thread pool worker thread id: {CurrentThread.ManagedThreadId}");
		}


		private static string Test(out int threadId)
		{
			WriteLine("Starting...");
			WriteLine($"Is thread pool thread: {CurrentThread.IsThreadPoolThread}");
			Sleep(TimeSpan.FromSeconds(2));
			threadId = CurrentThread.ManagedThreadId;
			return $"Thread pool worker thread id was: {threadId}";
		}
	}

    /**
	 * Starting...
Is thread pool thread: False
Thread id: 3
Starting...
Is thread pool thread: True
Thread pool worker thread id: 4
Thread pool worker thread id was: 4
Starting a callback...
State passed to a callbak: a delegate asynchronous call
Is thread pool thread: True
Thread pool worker thread id: 4
	 * **/
}

3. 向线程池中放入异步操作ThreadPool.QueueUserWorkItem 方法

using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;

namespace Chapter3.Recipe2
{
	class Program
	{
		static void Main(string[] args)
		{
			const int x = 1;
			const int y = 2;
			const string lambdaState = "lambda state 2";

            //QueueUser-WorkItem方法将该方法放到线程池中
            ThreadPool.QueueUserWorkItem(AsyncOperation);
			Sleep(TimeSpan.FromSeconds(1));

			ThreadPool.QueueUserWorkItem(AsyncOperation, "async state");
			Sleep(TimeSpan.FromSeconds(1));

			ThreadPool.QueueUserWorkItem( state => 
            {
				WriteLine($"Operation state: {state}");
				WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
				Sleep(TimeSpan.FromSeconds(2));
			}, "lambda state");

			ThreadPool.QueueUserWorkItem( _ =>
			{
				WriteLine($"Operation state: {x + y}, {lambdaState}");
				WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
				Sleep(TimeSpan.FromSeconds(2));
			}, "lambda state");

			Sleep(TimeSpan.FromSeconds(2));
			Console.ReadKey();

            /**
			 * 在操作完成后让线程睡眠一秒钟,从而让线程池拥有为新操作重用线程的可能性。
			 * 如果注释掉所有的Thread.Sleep调用,那么所有打印出的线程ID多半是不一样的。
			 * 如果ID是一样的,那很可能是前两个线程被重用来运行接下来的两个操作
			 * **/
        }

        private static void AsyncOperation(object state)
		{
			WriteLine($"Operation state: {state ?? "(null)"}");
			WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
			Sleep(TimeSpan.FromSeconds(2));
		}
	}
}

4. 在线程池中使用等待事件处理器及超时

using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;


namespace Chapter3.Recipe5
{
	class Program
	{
		static void Main(string[] args)
		{
			RunOperations(TimeSpan.FromSeconds(5));
			RunOperations(TimeSpan.FromSeconds(7));
            Console.ReadKey();
        }

		static void RunOperations(TimeSpan workerOperationTimeout)
		{
			using (var evt = new ManualResetEvent(false))
			using (var cts = new CancellationTokenSource())
			{
				WriteLine("Registering timeout operation...");
                //ThreadPool.RegisterWaitForSingleObject。该方法允许将回调函数放入线程池中的队列中
                //当提供的等待事件处理器收到信号或发生超时时,该回调函数将被调用
                var worker = ThreadPool.RegisterWaitForSingleObject(evt
                    , (state, isTimedOut) => WorkerOperationWait(cts, isTimedOut)
                    , null
                    , workerOperationTimeout
                    , true);

				WriteLine("Starting long running operation...");
				ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt));

				Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2)));
				worker.Unregister(evt);				
			}
		}

		static void WorkerOperation(CancellationToken token, ManualResetEvent evt)
		{
			for(int i = 0; i < 6; i++)
			{
				if (token.IsCancellationRequested)
				{
					return;
				}
				Sleep(TimeSpan.FromSeconds(1));
			}
			evt.Set();
		}

		static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut)
		{
			if (isTimedOut)
			{
				Console.WriteLine("超时...");
				cts.Cancel();
				WriteLine("Worker operation timed out and was canceled.");
			}
			else
			{
				WriteLine("Worker operation succeded.");
			}
		}
	}
}

当有大量的线程必须处于阻塞状态中等待一些多线程事件发信号时,以上方式非常有用。借助于线程池的基础设施,我们无需阻塞所有这样的线程。可以释放这些线程直到信号事件被设置。在服务器端应用程序中这是个非常重要的应用场景,因为服务器端应用程序要求高伸缩性及高性能 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值