C#当中的多线程_线程池
3.1 简介
线程池主要用在需要大量短暂的开销大的资源的情形。我们预先分配一些资源在线程池当中,当我们需要使用的时候,直接从池中取出,代替了重新创建,不用时候就送回到池当中。
.NET当中的线程池是受CLR来管理的。
.NET线程池有一个QueueUserWorkItem()的静态方法,这个方法接收一个委托,每当该方法被调用后,委托进入内部的队列中,如果线程池当中没有任何线程,此时创建一个新的工作线程,并将队列的第一个委托放入到工作线程当中。

注意点:
①线程池内的操作,尽量放入短时间运行的工作
②ASP.NET应用程序使用线程池不要把工作线程全部使用掉,否则web服务器将不能处理新的请求。
ASP.NET只推荐使用输入/输出密集型异步操作,因为其使用了一个不同的方式,叫做I/O线程。
③线程池当中的线程全部是后台线程,因此要注意前台线程执行完成后,后台线程也将结束工作。
3.2线程池中调用委托
首先要了解一个什么是【异步编程模型(Asynchronous Programming Model简称APM)】
.NET 1.0 异步编程模型(APM),
.NET 2.0 基于事件的异步编程模型(EAP),
.NET 4.0 基于任务的异步编程模型(TAP)。
本章主要了解什么是APM和EAP。
下面这篇文章介绍了异步编程模型,感觉挺好的,这里mark一下。
http://blog.csdn.net/xinke453/article/details/37810823
结合我这本书上的Demo,感觉理解起来无鸭梨,哈哈~
class Program
{
static void Main(string[] args)
{
int threadId = ;
RunOnThreadPool poolDelegate = Test;
//用创建线程的方法先创建了一个线程
var t = new Thread(() => Test(out threadId));
t.Start();
t.Join();
Console.WriteLine("Thread id: {0}", threadId);
/*
16 使用BeginInvoke来运行委托,Callback是一个回调函数,
17 "a delegate asynchronous call" 代表你希望转发给回调方法的一个对象的引用,
18 在回调方法中,可以查询IAsyncResult接口的AsyncState属性来访问该对象
19 */
IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "a delegate asynchronous call");
//这个例子当中使用AsyncWaitHandle属性来等待直到操作完成★
r.AsyncWaitHandle.WaitOne();
//操作完成后,会得到一个结果,可以通过委托调用EndInvoke方法,将IAsyncResult对象传递给委托参数。
string result = poolDelegate.EndInvoke(out threadId, r);
Console.WriteLine("Thread pool worker thread id: {0}", threadId);
Console.WriteLine(result);
Thread.Sleep(TimeSpan.FromSeconds());
}
private delegate string RunOnThreadPool(out int threadId);
private static void Callback(IAsyncResult ar)
{
Console.WriteLine("Starting a callback...");
Console.WriteLine("State passed to a callbak: {0}", ar.AsyncState);
Console.WriteLine("Is thread pool thread: {0}", Thread.CurrentThread.IsThreadPoolThread);
Console.WriteLine("Thread pool worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
}
private static string Test(out int threadId)
{
Console.WriteLine("Starting...");
Console.WriteLine("Is thread pool thread: {0}", Thread.CurrentThread.IsThreadPoolThread);
Thread.Sleep(TimeSpan.FromSeconds());
threadId = Thread.CurrentThread.ManagedThreadId;
return string.Format("Thread pool worker thread id was: {0}", threadId);

注意:在这个例子当中,主线程调用Thread.Sleep(TimeSpan.FromSeconds(2));如果没这句话,回调函数就不会被执行了,
以为线程池是后台线程,此时主线程结束,那么后台线程也跟着结束了,所以可能不会执行。
对于访问异步操作的结果,APM提供了四种方式供开发人员选择:
①在调用BeginXxx方法的线程上调用EndXxx方法来得到异步操作的结果,但是这种方式会阻塞调用线程,知道操作完成之后调用线程才继续运行。
②查询IAsyncResult的AsyncWaitHandle属性,从而得到WaitHandle,然后再调用它的WaitOne方法来使一个线程阻塞并等待操作完成再调用EndXxx方法来获得操作的结果。
(本例子当中使用了这个方法)
③循环查询IAsyncResult的IsComplete属性,操作完成后再调用EndXxx方法来获得操作返回的结果。
④使用 AsyncCallback委托来指定操作完成时要调用的方法,在操作完成后调用的方法中调用EndXxx操作来获得异步操作的结果。
★★★推荐使用第④种方法,因为此时不会阻塞执行BeginXxx方法的线程,然而其他三种都会阻塞调用线程,相当于效果和使用同步方法是一样,个人感觉根本失去了异步编程的特点,所以其他三种方式可以简单了解下,在实际异步编程中都是使用委托的方式。
3.3向线程池中加入异步操作
QueueUserWorkItem方法的定义!
[SecuritySafeCritical]
public static bool QueueUserWorkItem(WaitCallback callBack);
[SecuritySafeCritical]
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
实例:
class Program
{
static void Main(string[] args)
{
const int x = ;
const int y = ;
const string lambdaState = "lambda state 2";
//方法一,直接调用QueueUserWorkItem传入单个参数,作为回调函数
ThreadPool.QueueUserWorkItem(AsyncOperation);
Thread.Sleep(TimeSpan.FromSeconds()); //方法二,传入回调函数以及状态参数
ThreadPool.QueueUserWorkItem(AsyncOperation, "async state");
Thread.Sleep(TimeSpan.FromSeconds()); //方法三,使用labmbda表达式
ThreadPool.QueueUserWorkItem( state => {
Console.WriteLine("Operation state: {0}", state);
Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(TimeSpan.FromSeconds());
}, "lambda state"); //方法四,使用闭包机制
ThreadPool.QueueUserWorkItem( _ =>
{
Console.WriteLine("Operation state: {0}, {1}", x+y, lambdaState);
Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(TimeSpan.FromSeconds());
}, "lambda state"); Thread.Sleep(TimeSpan.FromSeconds());
} private static void AsyncOperation(object state)
{
Console.WriteLine("Operation state: {0}", state ?? "(null)");
Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(TimeSpan.FromSeconds());
}

扩展:C#闭包(Closure)机制是什么?
3.4线程池与并行度
下面这个实例展示线程池如何工作于大量异步操作,以及他和创建的大量单独线程方式的区别。
class Program
{
static void Main(string[] args)
{
const int numberOfOperations = ;
var sw = new Stopwatch();
sw.Start();
UseThreads(numberOfOperations);
sw.Stop();
Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds); sw.Reset();
sw.Start();
UseThreadPool(numberOfOperations);
sw.Stop();
Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds);
} static void UseThreads(int numberOfOperations)
{
using (var countdown = new CountdownEvent(numberOfOperations))
{
Console.WriteLine("Scheduling work by creating threads");
for (int i = ; i < numberOfOperations; i++)
{
var thread = new Thread(() => {
Console.Write("{0},", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(TimeSpan.FromSeconds(0.1));
countdown.Signal();
});
thread.Start();
}
countdown.Wait();
Console.WriteLine();
}
} static void UseThreadPool(int numberOfOperations)
{
using (var countdown = new CountdownEvent(numberOfOperations))
{
Console.WriteLine("Starting work on a threadpool");
for (int i = ; i < numberOfOperations; i++)
{
ThreadPool.QueueUserWorkItem( _ => {
Console.Write("{0},", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(TimeSpan.FromSeconds(0.1));
countdown.Signal();
});
}
countdown.Wait();
Console.WriteLine();
}
}
}


分别用创建大量线程的方式和线程池的方式执行500个Thread.Sleep(TimeSpan.FromSeconds(0.1))操作,
我们发现线程池花费了更多的时间,但是占用的资源数目很少(通过ThreadId来看)。
3.5实现一个取消选项
使用CancellationTokenSource和CancellationToken两个类来实现工作线程工作的取消操作。
实例:
class Program
{
static void Main(string[] args)
{
using (var cts = new CancellationTokenSource())
{
CancellationToken token = cts.Token;
ThreadPool.QueueUserWorkItem(_ => AsyncOperation1(token));
Thread.Sleep(TimeSpan.FromSeconds());
cts.Cancel();
} using (var cts = new CancellationTokenSource())
{
CancellationToken token = cts.Token;
ThreadPool.QueueUserWorkItem(_ => AsyncOperation2(token));
Thread.Sleep(TimeSpan.FromSeconds());
cts.Cancel();
} using (var cts = new CancellationTokenSource())
{
CancellationToken token = cts.Token;
ThreadPool.QueueUserWorkItem(_ => AsyncOperation3(token));
Thread.Sleep(TimeSpan.FromSeconds());
cts.Cancel();
} Thread.Sleep(TimeSpan.FromSeconds());
} /// <summary>
/// 第一种采用轮询IsCancellationRequested属性的方式,如果为true,那么操作被取消
/// </summary>
/// <param name="token"></param>
static void AsyncOperation1(CancellationToken token)
{
Console.WriteLine("Starting the first task");
for (int i = ; i < ; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("The first task has been canceled.");
return;
}
Thread.Sleep(TimeSpan.FromSeconds());
}
Console.WriteLine("The first task has completed succesfully");
}
/// <summary>
/// 抛出一个OperationCancelledException异常
/// 这个允许操作之外控制取消过程,即需要取消操作的时候,通过操作之外的代码来处理
/// </summary>
/// <param name="token"></param>
static void AsyncOperation2(CancellationToken token)
{
try
{
Console.WriteLine("Starting the second task"); for (int i = ; i < ; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(TimeSpan.FromSeconds());
}
Console.WriteLine("The second task has completed succesfully");
}
catch (OperationCanceledException)
{
Console.WriteLine("The second task has been canceled.");
}
}
/// <summary>
/// 第三种注册一个回调函数,当操作被取消时候,调用回调函数
/// </summary>
/// <param name="token"></param>
private static void AsyncOperation3(CancellationToken token)
{
bool cancellationFlag = false;
token.Register(() => cancellationFlag = true);
Console.WriteLine("Starting the third task");
for (int i = ; i < ; i++)
{
if (cancellationFlag)
{
Console.WriteLine("The third task has been canceled.");
return;
}
Thread.Sleep(TimeSpan.FromSeconds());
}
Console.WriteLine("The third task has completed succesfully");

CancellationTokenSource和CancellationToken两个类是.net4.0一会引入的,目前是实现异步操作取消事实标准。
3.6在线程池中使用等待事件处理器和超时
使用线程池当中的Threadpool.RegisterWaitSingleObject类来进行事件案处理。
RegisterWaitSingleObject的原型如下:
public static RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject,
WaitOrTimerCallback callBack,
Object state,
int millisecondsTimeOutInterval,
bool executeOnlyOnce
)
参数
waitObject
要注册的 WaitHandle。使用 WaitHandle 而非 Mutex。
callBack
waitObject 参数终止时调用的 WaitOrTimerCallback 委托。
state
传递给委托的对象。
timeout
TimeSpan 表示的超时时间。如果 timeout 为零,则函数测试对象的状态并立即返回。如果 timeout 为 -1,则函数的超时间隔永远不过期。
executeOnlyOnce
如果为 true,表示在调用了委托后,线程将不再在 waitObject 参数上等待;如果为 false,表示每次完成等待操作后都重置计时器,直到注销等待。
返回值
封装本机句柄的 RegisteredWaitHandle。
相信看了这些之后大家还是一头雾水,这个方法的做用是向线程池添加一个可以定时执行的方法,第四个参数millisecondsTimeOutInterval 就是用来设置间隔执行的时间,但是这里第五个参数executeOnlyOnce 会对第四个参数起作用,当它为true时,表示任务仅会执行一次,就是说它不会,像Timer一样,每隔一定时间执行一次,这个功能的话用Timer控件也可以实现
该方法还在此基础上提供了基于信号量来触发执行任务。
信号量也叫开关量,故名思议,它只有两种状态,不是true就是false,
WaitHandle就是这类开关量的基础类,继承它的类有Mutex,ManualResetEvent,AutoResetEvent,一般我们使用后两个
写法:
static ManualResetEvent wait2=new ManualResetEvent(false);
static AutoResetEvent wait=new AutoResetEvent(false);
我们可以在将其实例化时指定开关量的初始值。(true为有信号,false为没信号)
ManualResetEvent和AutoResetEvent的区别在于:
前者调用Set方法后将自动将开关量值将一直保持为true,后者调用Set方法将变为true随后立即变为false,可以将它理解为一个脉冲。
例子
class Program
{
static void Main(string[] args)
{
//执行两次RunOperations操作,第一次会超时,第二次不会超时
RunOperations(TimeSpan.FromSeconds());
RunOperations(TimeSpan.FromSeconds());
} static void RunOperations(TimeSpan workerOperationTimeout)
{
//定义一个ManualResetEvent信号量,初始为false
using (var evt = new ManualResetEvent(false))
//实例化一个CancellationTokenSource实例,用于取消操作
using (var cts = new CancellationTokenSource())
{
Console.WriteLine("Registering timeout operations...");
//注册超时的被调用的回调函数。
var worker = ThreadPool.RegisterWaitForSingleObject(
evt,
(state, isTimedOut) => WorkerOperationWait(cts, isTimedOut),
null,
workerOperationTimeout,
true ); Console.WriteLine("Starting long running operation...");
//线程池执行WorkerOperation操作
ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt)); Thread.Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds()));
worker.Unregister(evt);
}
} /// <summary>
/// 线程池内需要被调用的操作
/// </summary>
/// <param name="token"></param>
/// <param name="evt"></param>
static void WorkerOperation(CancellationToken token, ManualResetEvent evt)
{
for(int i = ; i < ; i++)
{
if (token.IsCancellationRequested)
{
return;
}
Thread.Sleep(TimeSpan.FromSeconds());
}
//设置信号量,此时evt为true。
evt.Set();
} /// <summary>
/// 超时时候执行的回调函数
/// </summary>
/// <param name="cts"></param>
/// <param name="isTimedOut"></param>
static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut)
{
if (isTimedOut)
{
cts.Cancel();
Console.WriteLine("Worker operation timed out and was canceled.");
}
else
{
Console.WriteLine("Worker operation succeded.");
}

3.7在线程池中使用计时器
使用system.Threading.Timer对象来在线程池中创建周期性调用的异步操作。
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press 'Enter' to stop the timer...");
DateTime start = DateTime.Now;
//实例化这个timer类
//一秒后执行TimerOperation这个操作,然后每隔2秒执行一次
_timer = new Timer(
_ => TimerOperation(start),
null,
TimeSpan.FromSeconds(),
TimeSpan.FromSeconds()); Thread.Sleep(TimeSpan.FromSeconds()); //改变计时器的运行时间,一秒后执行TimerOperation,然后每隔4秒执行一次
_timer.Change(TimeSpan.FromSeconds(), TimeSpan.FromSeconds()); Console.ReadLine(); _timer.Dispose();
} static Timer _timer; static void TimerOperation(DateTime start)
{
TimeSpan elapsed = DateTime.Now - start;
Console.WriteLine("{0} seconds from {1}. Timer thread pool thread id: {2}", elapsed.Seconds, start,
Thread.CurrentThread.ManagedThreadId);
}

3.8使用BackgroudWorker组件
本小节将介绍一个异步编程模式的另一种方式,叫基于事件的异步模式(EAP)
先看一个例子吧:
class Program
{
static void Main(string[] args)
{
//实例化一个BackgroundWorker类
var bw = new BackgroundWorker();
//获取或设置一个值,该值指示 BackgroundWorker 能否报告进度更新。
bw.WorkerReportsProgress = true;
//设置后台工作线程是否支持取消操作
bw.WorkerSupportsCancellation = true; //给DoWork、ProgressChanged、RunWorkerCompleted事件绑定处理函数
bw.DoWork += Worker_DoWork;
bw.ProgressChanged += Worker_ProgressChanged;
bw.RunWorkerCompleted += Worker_Completed; //启动异步操作
bw.RunWorkerAsync(); Console.WriteLine("Press C to cancel work");
do
{
if (Console.ReadKey(true).KeyChar == 'C')
{
//取消操作
bw.CancelAsync();
}
}
while(bw.IsBusy);
} static void Worker_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("DoWork thread pool thread id: {0}", Thread.CurrentThread.ManagedThreadId);
var bw = (BackgroundWorker) sender;
for (int i = ; i <= ; i++)
{ if (bw.CancellationPending)
{
e.Cancel = true;
return;
} if (i% == )
{
bw.ReportProgress(i);
} Thread.Sleep(TimeSpan.FromSeconds(0.1));
}
e.Result = ;
} static void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine("{0}% completed. Progress thread pool thread id: {1}", e.ProgressPercentage,
Thread.CurrentThread.ManagedThreadId);
} static void Worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("Completed threadpool thread id:{0}",Thread.CurrentThread.ManagedThreadId);
if (e.Error != null)
{
Console.WriteLine("Exception {0} has occured.", e.Error.Message);
}
else if (e.Cancelled)
{
Console.WriteLine("Operation has been canceled.");
}
else
{
Console.WriteLine("The answer is: {0}", e.Result);
}
}
|
名称 |
说明 |
|
|
调用 RunWorkerAsync 时发生。 RunWorkerAsync 方法提交一个启动以异步方式运行的操作的请求。发出该请求后,将引发 DoWork 事件,该事件随后开始执行后台操作。 如果后台操作已在运行,则再次调用 RunWorkerAsync 将引发 InvalidOperationException。 |
||
|
调用 ReportProgress 时发生。 public void ReportProgress ( percentProgress 已完成的后台操作所占的百分比,范围从 0% 到 100%。 如果您需要后台操作报告其进度,则可以调用 ReportProgress 方法来引发 ProgressChanged 事件。 WorkerReportsProgress 属性值必须是 您需要实现一个有意义的方法,以便按照占已完成的总任务的百分比来度量后台操作的进度。 对 ReportProgress 方法的调用为异步且立即返回。The ProgressChanged 事件处理程序在创建 BackgroundWorker 的线程上执行。 |
||
|
当后台操作已完成、被取消或引发异常时发生。 |
||
|
在该方法中可以知道操作是成功完成还是发生错误,亦或被取消。 |
貼り付け元 <https://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker.aspx>
成功完成的时候

任务取消的时候

C#当中的多线程_线程池的更多相关文章
- java ->多线程_线程池
线程池概念 线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源. 我们详细的解释一下为什么要使用线程池?(程序优化) 在jav ...
- Java多线程_线程池
作用我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为 ...
- C#当中的多线程_线程同步
第2章 线程同步 原来以为线程同步就是lock,monitor等呢,看了第二章真是大开眼界啊! 第一章中我们遇到了一个叫做竞争条件的问题.引起的原因是没有进行正确的线程同步.当一个线程在执行操作时候, ...
- C#当中的多线程_线程基础
前言 最近工作不是很忙,想把买了很久了的<C#多线程编程实战>看完,所以索性把每一章的重点记录一下,方便以后回忆. 第1章 线程基础 1.创建一个线程 using System; usin ...
- C#多线程之线程池篇3
在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关 ...
- C#多线程之线程池篇2
在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...
- Java 多线程之线程池的使用
一. 使用背景 谈到Java多线程,我们很自然的会想到并发,在编写多线程代码时,我们一般会创建多个线程,如果并发的线程数量很多,而且每个线程都是执行一个时间很短的任务就结束了,这样频繁的进行线程的创建 ...
- Qt中的多线程与线程池浅析+实例
1. Qt中的多线程与线程池 今天学习了Qt中的多线程和线程池,特写这篇博客来记录一下 2. 多线程 2.1 线程类 QThread Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一 ...
- C#多线程之线程池篇1
在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...
随机推荐
- 【转】 Android快速开发系列 10个常用工具类 -- 不错
原文网址:http://blog.csdn.net/lmj623565791/article/details/38965311 转载请标明出处:http://blog.csdn.net/lmj6235 ...
- android 中对apache httpclient及httpurlconnection的选择
在官方blog中,android工程师谈到了如何去选择apache client和httpurlconnection的问题: 原文见http://android-developers.blogspot ...
- MVVM模式应用体会
转自:http://www.cnblogs.com/626498301/archive/2011/04/08/2009404.html 进公司实习工作后,本人接触的第一个技术名语就是MVVM模式,从学 ...
- 滚动轮播插件——jCarouselLite
jcarousellite(上下.水平滚动元素插件)插件使用: 参数说明: btnPrev string 上一个按钮的class名, 比如 btnPrev: ".prev" ...
- HTML5 移动应用开发环境搭建及原理分析
开发环境搭建: 一.Android 开发平台搭建 安装java jdk:\\10.194.151.132\Mewfile\tmp\ADT 配置java jdk 1) 新建系统变量,JAVA_HOME ...
- 转载:C++之高精度算法
C++之高精度算法 注意:本文转载自http://blog.sina.com.cn/s/blog_4fdb102b010087ng.html,十分感谢原作者:忍者 前言:由于计算机运算是有模运算 ...
- ios 页面滑入滑出
从左边滑进 CGRect r1,r2; r1 = app.testview.view.frame; r2 = self.view.frame; [app.testview.view setFrame: ...
- dnspod-sr内网轻量级DNS首选方案 - 运维生存时间
dnspod-sr内网轻量级DNS首选方案 - 运维生存时间 undefined
- Yii 的AR单行数据自动缓存机制
相关的YII类: CActiveRecord CActiveRecordBehavior cache Active Record Active Record (AR) 是一个流行的 对象-关系映射 ( ...
- 问题-安装XP时,提示不识别SATA硬盘
问题现象:笔记本装XP时,系统提示硬盘不能被识别? 问题描述:我原来是vista版本的,硬盘是SATA接口,但我觉得用vista不习惯,所以想装XP,可后来发现机器无法识别硬盘,该怎么解决? 问题原因 ...