http://www.jb51.net/article/46257.htm
http://blog.gkarch.com/threading/part3.html
http://www.111cn.net/net/160/77806.htm
http://www.yoda.arachsys.com/csharp/threads/index.shtml
http://www.codeproject.com/Articles/26148/Beginners-Guide-to-Threading-in-NET-Part-of-n
https://msdn.microsoft.com/en-us/library/aa645740(v=vs.71).aspx
http://www.360doc.com/content/11/0608/15/1039473_122473097.shtml
http://www.cnblogs.com/kissdodog/archive/2013/03/28/2986026.html
线程池:http://www.cnblogs.com/henw/archive/2012/01/06/2314870.html
http://www.cnblogs.com/JeffreyZhao/archive/2009/07/22/thread-pool-1-the-goal-and-the-clr-thread-pool.html
在.NET FrameWork 4.0之前,如果我们使用线程。一般有以下几种方式:
1.使用System.Threading.Thread 类,调用实例方法Start()开启一个新线程,调用Abort()方法来提前终止线程。
2.使用System.Threading.ThreadPool类,调用静态方法QueueUserWorkItem(),将方法放入线程池队列,线程池来控制调用。
3.使用BeginInvoke,EndInvoke,BeginRead,EnRead,BeginWrite,EndWrite等一系列的异步方法(委托)。
4.使用System.ComponentModel.BackgroundWorker控件,调用实例方法RunWorkerAsync(),开启一个新线程。
以上线程方式在需要执行一个复杂的异步操作时,只能使用CLR线程池技术来执行一个任务。线程池执行异步任务时,不知道任务何时完成,以及任务的在任务完成后不能获取到返回值。但是在.NET FrameWork4.0中引人了一个的任务(System.Threading.Tasks命名空间的类型)机制来解决异步操作完成时间和完成后返回值的问题。
速度(最快为1) | 返回值 | 多参数 | 等待在时限内完成 | 超时后结束 | |
ThreadPool.UnsafeQueueUserWorkItem() | 1 | 非原生支持1 | 非原生支持 | 非原生支持3 | 不支持 |
ThreadPool.QueueUserWorkItem() | 2.7 | 非原生支持1 | 非原生支持 | 非原生支持3 | 不支持 |
Task() | 4.5 | 支持2 | 非原生支持 | 支持 | 自愿结束 |
Delegate.BeinInvoke() | 25.4 | 非原生支持1 | 支持 | 支持4 | 不支持 |
Thread.Start() | 11009 | 非原生支持1 | 非原生支持 | 非原生支持3 | 支持 |
一.线程池:
ThreadPool(线程池)是一个静态类,它没有定义任何的构造方法(),我们只能够使用它的静态方法,这是因为ThreadPool是托管线程池,是由CLR管理的。
ThreadPool使用WaitCallback委托,它所要做的工作是在后台进行的。使工作项的排队和运行更容易,可以给工作者线程传递一个状态对象(提供数据)。状态对象是私有的作用域位于线程层,所以不需要进行同步。
ThreadPool目标是为了减除线程的初始化开销,实现并行处理。.NET类库中的ThreadPool是异步IO的基础,比如,在System.Net.Socket中,我们可以使用BeginAccept , EndAccept将Socket需要阻塞的操作放到系统的线程池中运行,而在执行结束以后通知主线程。
一个ThreadPool里面注册的线程拥有默认的堆栈大小,默认的优先级。并且,他们都存在于多线程空间(Multithreaded apartment)中。
ThreadPool中的Thread不能手动取消,也不用手动开始。所以ThreadPool并不适用比较长的线程。你要做的只是把一个WaitCallback委托塞给ThreadPool,然后剩下的工作将由系统自动完成。系统会在ThreadPool的线程队列中一一启动线程。
线程池中的线程执行完之后是没有返回值的.
1.将任务添加进线程池:
ThreadPool.QueueUserWorkItem(new WaitCallback(方法名));
重载
ThreadPool.QueueUserWorkItem(new WaitCallback(方法名), 参数);
2.对于线程池主要的控制有控制线程数大小:
当线程池满时,多余的线程会在队列里排队,当线程池空闲时,系统自动调入排队的线程,以保持系统利用率。
/通常是将计算密集型的操作放在worker线程池中运行,而线程池的大小会根据当前的CPU使用量自动调整,通过下面两个方法,我们可以设置线程池的大小:
//ThreadPool.SetMaxThreads(10, 200);
//ThreadPool.SetMinThreads(2, 40);</span>
<span style="font-size:14px;"><span style="font-size:18px;">public static bool SetMaxThreads(
int workerThreads,
int completionPortThreads
)
参数:
workerThreads
类型:System.Int32
线程池中辅助线程的最大数目。
completionPortThreads
类型:System.Int32
线程池中异步 I/O 线程的最大数目.
3.如何判断线程池中的线程执行完成:
操作系统提供了一种”信号灯”(ManualResetEvent),允许线程通过发信号互相通信。
当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态,此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。一旦它被终止,ManualResetEvent 将保持终止状态(即对 WaitOne 的调用的线程将立即返回,并不阻塞),直到它被手动重置。可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
eventX.WaitOne(Timeout.Infinite, true); 阻止当前线程,直到当前 WaitHandle 收到信号为止(即直到当前线程完成).
eventX.Set(); 将事件状态设置为终止状态,允许一个或多个等待线程继续。
4.线程池的取消操作:
FrameWork提供了一个取消操作的模式,这就意味着我们可以取消正在执行的操作,对于耗时的操作来说,是非常好的用户体验。为了取消一个操作,要创建一个System.Threading.CancellationTokenSource类的实例。
这个对象包含了管理和取消的所有状态,Token属性可以获取CancellationToken的实例,可以根据IsCancellationRequested属性来判断是否需要取消操作。同时,可以通Register方法注册一个在取消时调用的委托。
举例:
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.Token.Register(() => Console.WriteLine("Register"));
Console.WriteLine("Main");
ThreadPool.QueueUserWorkItem((o) =>
{
CancellationToken ct = (CancellationToken)o;
for (int i = 0; i < 100; i++)
{
//是否需要取消操作
if (ct.IsCancellationRequested)
{
break;
}
Console.WriteLine(DateTime.Now);
Thread.Sleep(100);
}
}, cts.Token);
Thread.Sleep(1000);
//取消
cts.Cancel();
Console.WriteLine("Main Next...");
Console.Read();
}
可以看到在Main方法中创建了CancellationTokenSource的实例,同时注册了一个在取消时调用的委托,并且把这个实例传给了线程池方法。在线程池方法的循环内判断是否需要取消任务,最后在Main方法内调用cts.Cancel()取消了操作。
5.举例:
class Program
{
static void Main(string[] args)
{
using (ManualResetEvent m1 = new ManualResetEvent(false))
{
Thr t = new Thr(m1);
ThreadPool.QueueUserWorkItem(delegate
{
t.MethodA();
});
m1.WaitOne(Timeout.Infinite, true);
}
Console.WriteLine("ending");
Console.ReadLine();
}
}
public class Thr
{
public int iCount = 0;
public ManualResetEvent eventX;
public Thr(ManualResetEvent eventX)
{
this.eventX = eventX;
}
public void MethodA()
{
for (int i = 0; i < 20; i++)
{
Console.WriteLine(i);
}
eventX.Set();
}
}
class Program
{
static void Main(string[] args)
{
ManualResetEvent eventX=new ManualResetEvent (false);
ThreadPool.SetMaxThreads(3, 3);
thr t = new thr(15, eventX);
for (int i = 0; i < 15; i++)
{
// t.ThreadProc(i);
ThreadPool.QueueUserWorkItem(new WaitCallback(t.ThreadProc), i);
}
eventX.WaitOne(Timeout.Infinite, true);
Console.WriteLine("testing");
Thread.Sleep(10000);
Console.WriteLine("ending");
}
}
public class thr
{
public static int iCount = 0;
public static int iMaxCount = 0;
public ManualResetEvent eventX;
public thr(int count, ManualResetEvent mre)
{
iMaxCount = count;
eventX = mre;
}
public void ThreadProc(object i)
{
Console.WriteLine("Thread ["+i.ToString()+"]");
Thread.Sleep(2000);
Interlocked.Increment(ref iCount);
if (iCount == iMaxCount)
{
Console.WriteLine("send ending info");
eventX.Set();
}
}
}
在以下情况中不宜使用ThreadPool而应该使用单独的Thread:
//1,需要为线程指定详细的优先级
//2,线程执行需要很长时间
//3,需要把线程放在单独的线程apartment中
//4,在线程执行中需要对线程操作,如打断,挂起等。
二、Timer:
考虑如何用ThreadPool来调度一些周期性运行的工作,.NET提供了System.Threading.Timer类实现这一个功能。涉及Timer和TimerCallback。后者也是一个委托,其声明如下:
public delegate void TimerCallback(object state);
显然,他的使用方法与上面WaitCallback的完全相同,我们可以简单的将上面的例子变成周期性运行的:Timer tm = new Timer(new TimerCallback(DoWork) , new testObject() , 0 , 2000);
后面的两个参数是启动的延迟时间和周期Timer的线程分配机制与当前同时进行的其它Timer的时间复杂度有关系,当定义几个Timer同时工作的时候,如果每一个操作耗时较长,而且可能同时到期的话,线程池可能为每一个Timer操作定义不同的执行线程,而对于简单操作,有可能多个Timer被放在同一个线程中执行。
三、Task and Task Factory:所有Task都是后台线程
1.Task以及Task.Factory都是在.Net 4引用的。Task跟Thread很类似.
static public void ThreadMain()
{
Thread t1 = new Thread(TaskWorker);
t1.Start(3);
}
static public void TaskMain()
{
Task t1 = new Task(TaskWorker, 3, TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent);
Console.WriteLine(t1.Status);
t1.Start();
t1.Wait(); // need to wait for finishing.
}
static public void TaskWorker(object state)
{
int nTime = (int)state;
for (int i = 0; i < nTime; i++)
{
Thread.Sleep(100);
Console.WriteLine(string.Format("current thread {0} sleep for {1} miniseconds .", Task.CurrentId, (i + 1) * 100));
}
return;
}
2.task可以支持工作函数具有返回值(Func<TRessult>()或者Func<object, TResult>):
static public int TaskWorkerWithReturn(object state)
{
int nTime = (int)state;
for (int i = 0; i < nTime; i++)
{
Thread.Sleep(100);
Console.WriteLine(string.Format("current thread {0} sleep for {1} miniseconds .", Task.CurrentId, (i + 1) * 100));
}
nTime++;
return nTime;
}
Task<int> t2 = new Task<int>(TaskWorkerWithReturn, 3, TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent);
t2.Start();
t2.Wait();
Console.WriteLine(t2.Result); //task的类型必须和调用的函数的返回值类型一致。Result即为返回值
3.不管从工作函数是否有返回值,task都需要在其运行过程中至少有一个前台线程在跑,否则会直接退出,根本原因是所有task都是后台线程。task的工作函数的输入参数类型职能是object。
4.要注意的是调用task.Result获取返回值,或者是task.Wait()等待任务执行完成,主线程将会被阻塞。要等到Task执行完成才会继续执行。同时,如果Task内部抛出了一个未处理的异常,这个异常会在调用Result或者Wait()的时候会抛出System.AggregateException。
除了Wait()方法,Task类还有一些静态方法,WaitAll用于等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程和Wait用于等待提供的任一个 System.Threading.Tasks.Task 对象完成执行过程,这两个方法都有一些重载版本。
//等待所有任务完成
public static void WaitAll(params Task[] tasks);
//等待任意一个任务完成
public static int WaitAny(params Task[] tasks);
5.同步:
对于没有返回值的工作函数需要通过Task内核对象来同步主调线程(例如task内置的事件,使用wait来阻塞等待);
对于有返回值的工作函数可以通过访问Task的Result属性来实现阻塞等待。
6.异步调用:
在使用能够Task类的Wait方法等待一个任务时或派生类的Result属性获得任务执行结果都有可能阻塞线程,为了解决这个问题可以使用ContinueWith方法,他能在一个任务完成时自动启动一个新的任务来处理执行结果。作为新的一个特性在.net 4中引入,task能实现丰富的异步调用,使用成员函数ContinueWith来响应异步工作函数的完成(一个任务完成后自动执行一个新任务)(注意,不一定由之前完成异步函数的线程执行)。这个方法可以在一个任务完成时,启动一个新任务,并不阻塞主线程。
Task cwt = task.ContinueWith(t => {
Console.WriteLine("任务完成后的执行结果:{0}", t.Result.ToString());
});
static public void TaskMain()
{
Task<int> t3 = new Task<int>(FirstTask, 1);
t3.Start();
Task<int> t4 = t3.ContinueWith<int>(RecusiveTask);
Task<int> t5 = t4.ContinueWith<int>(RecusiveTask);
Task<int> t6 = t5.ContinueWith<int>(RecusiveTask).ContinueWith<int>(RecusiveTask);
//Console.WriteLine(string.Format("final result: {0}", t6.Result));
}
static public int FirstTask(object state)
{
int data = (int)state;
for (int i = 0; i < data; i++)
{
Thread.Sleep(100);
Console.WriteLine(string.Format("current thread {0} slept for {1} milisecond.", Task.CurrentId, (i + 1) * 100));
}
data++;
return data;
}
static public int RecusiveTask(Task<int> T) //参数T表示上一个完成的Task
{
int data = T.Result; //上一个Task完成后返回的结果
for (int i = 0; i < data; i++)
{
Thread.Sleep(100);
Console.WriteLine(string.Format("current thread {0} slept for {1} milisecond.", Task.CurrentId, (i + 1) * 100));
}
data++;
return data;
}
输出:
current thread 1 slept for 100 milisecond.
current thread 2 slept for 100 milisecond.
current thread 2 slept for 200 milisecond.
current thread 3 slept for 100 milisecond.
current thread 3 slept for 200 milisecond.
current thread 3 slept for 300 milisecond.
current thread 4 slept for 100 milisecond.
current thread 4 slept for 200 milisecond.
current thread 4 slept for 300 milisecond.
current thread 4 slept for 400 milisecond.
current thread 5 slept for 100 milisecond.
current thread 5 slept for 200 milisecond.
current thread 5 slept for 300 milisecond.
current thread 5 slept for 400 milisecond.
current thread 5 slept for 500 milisecond.
final result: 6
上述示例中任务不是等待完成来显示执行结果,而是使用ContinueWith方法,它能够知道任务在什么时候完成并启动一个新的任务来执行任务完成后的处理
7. 任务的状态:
Task内部有Status的只读属性,这个的属性是TaskStatus类型的枚举。在Task对象的生存期间,可以通过Status获取任务的的当前状态。这个枚举的状态的定义如下
public enum TaskStatus
{
Created = 0, //该任务已初始化,但尚未被计划
WaitingForActivation = 1, //该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。
WaitingToRun = 2, //该任务已被计划执行,但尚未开始执行。
Running = 3, //该任务正在运行,但尚未完成。
WaitingForChildrenToComplete = 4,//该任务已完成执行,正在隐式等待附加的子任务完成。
RanToCompletion = 5, //已成功完成执行的任务。
Canceled = 6, //该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,
// 此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的CancellationToken 发出了信号。
Faulted = 7, // 由于未处理异常的原因而完成的任务。
}
在创建一个Task对象时,状态是Created。当调用Start()方法,任务启动时,状态变成了WaitingToRun(),任务真正开始执行时,状态变成了Running,任务结束时,对应的三种不同的状态:成功、被取消、执行中出现未处理异常,分别对应:RanToCompletion、Canceled、Faulted。
8. TaskFactory和 TaskFactory<TRsult >类:
如果需要创建一组具有相同状态的任务时,可以使用TaskFactory类或TaskFactory<TResult>类。这两个类创建一组任务时可以指定任务的CancellationToken、TaskCreationOptions、TaskContinuationOptions和TaskScheduler默认值:
public enum TaskStatus
{
Created = 0,
WaitingForActivation = 1,
WaitingToRun = 2,
Running = 3,
WaitingForChildrenToComplete = 4,
RanToCompletion = 5,
Canceled = 6,
Faulted = 7,
}
static void Main(string[] args)
{
Task parent = new Task(() =>
{
CancellationTokenSource cts = new CancellationTokenSource(5000);
//创建任务工厂
TaskFactory tf = new TaskFactory(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
//添加一组具有相同状态的子任务
Task[] task = new Task[]{
tf.StartNew(() => { Console.WriteLine("1"); }),
tf.StartNew(() => { Console.WriteLine("2"); }),
tf.StartNew(() => { Console.WriteLine("3"); })
};
});
parent.Start();
Console.Read();
}
输出:1
3
2
9.任务内部实现和任务调度
任务内部有一组构成任务状态的属性,标识任务的唯一Id、表示任务的执行状态(TaskStatus)、任务创建时提供的回调函数的引用和传递给回调函数的数据对象AsyncState、对任务创建时的任务调度对象(TaskScheduler)的引用、对父任务的引用以及对执行上下文的引用和ManualResetEventSlim对象的引用。Task类和Task<TResult>类都实现了标准的释放资源的接口,允许在任务完成处理的时候使用Dispose方法释放资源(关闭ManualResetEventSlim对象实例)。可以使用Task类的CurrentId属性获得正在执行的任务的Id,如果没有任务在执行CurrentId返回值为null,CurrentId是一个int?可空类型的属性。任务执行的生命周期通过TaskStatus类型的一个值来表示,TaskStatus所包含的值:
我们可以通过Task类的Exception属性获得任务在执行过程中的所有异常,Exception是一个AggregateException类型的属性。Task类提供了IsCanceled、IsCompleted、IsFaulted属性来获得任务的完成状态。通过ContinueWith、ContinueWhenAll、ContinueWhenAny和FromAsync创建的后续任务都处于WaitingForActivation 状态,这个状态的任务会在父任务完成后自动执行。
在任务内部由TaskScheduler类调度任务的执行,该类是一个抽象类,FCL中从他派生了两个派生类:ThreadPoolTaskScheduler线程池任务调度器和SynchronizationContextTaskScheduler同步上下文任务调度器。所有任务默认都是采用ThreadPoolTaskScheduler调度任务,他是采用线程池来执行任务,可以通过TaskScheduler类的静态属性Default获得对默认任务调度器的引用。SynchronizationContextTaskScheduler任务调度器能够用在Window form、WPF等应用程序,他的任务调度是采用的GUI线程,所以他能同步更新UI组件,可以通过TaskScheduler类的静态方法FromCurrentSynchronizationContext获得对一个同步上下文任务调度起的引用。
任务调度示例:
private void button1_Click(object sender, EventArgs e)
{
//获得同步上下文任务调度器
TaskScheduler m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
//创建任务,并采用默认任务调度器(线程池任务调度器)执行任务
Task<int> task = new Task<int>(() =>
{
//执行复杂的计算任务。
Thread.Sleep(2000);
int sum = 0;
for (int i = 0; i < 100; i++)
{
sum += i;
}
return sum;
});
var cts=new CancellationTokenSource();
//任务完成时启动一个后续任务,并采用同步上下文任务调度器调度任务更新UI组件。
task.ContinueWith(t => {this.label1.Text="采用SynchronizationContextTaskScheduler任务调度器更新UI。\r\n计算结果是:"+task.Result.ToString(); },
cts.Token ,TaskContinuationOptions.AttachedToParent,m_syncContextTaskScheduler);
task.Start();
}
四、async 和wait:
http://www.wxzzz.com/663.html
http://blog.csdn.net/tianmuxia/article/details/17675681/
在.NET4.5中主要推出的是异步编程,而实现异步编程的简单方式就是使用await和async关键字,而在新的.net托管类库中已经大大使用了这两个关键字,我们在vs2010添加补丁和vs2012中都可以使用它