多线程学习
多线程学习心得
一:为什么要用线程:
使用线程可以提高系统的性能,比如系统请求大量数据的时候,把一些工作交由异步线程去完成(如:输出工作),使主线程保持稳定去处理其他事情。
优点:提高系统的性能。
详细描述:
(1)多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态;
(2)当前没有进行处理的任务时可以将处理器时间让给其它任务;
(3)占用大量处理时间的任务可以定期将处理器时间让给其它任务;
(4)可以随时停止任务;
(5)可以分别设置各个任务的优先级以优化性能。
缺点:CPU开销增大。会出现线程死锁导致出错
详细描述:
(1)等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。
(2)对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。
(3)线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
(4)对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误是程序员无法预知的。
什么情况下需要使用
是否需要创建多个线程取决于各种因素。在以下情况下,最适合采用多线程处理:
(1)耗时或大量占用处理器的任务阻塞用户界面操作;
(2)各个任务必须等待外部资源 (如远程文件或 Internet连接)。
注意:需要花费不少的时间在线程的切换上,所以过多地使用多线程反而会导致性能的下降。
推介浏览:http://www.cnblogs.com/leslies2/archive/2012/02/07/2310495.html#t2
二:线程的基本知识
1.1System.Threading.Thread类基本属性
属性名称 |
说明 |
CurrentContext |
获取线程正在其中执行的当前上下文。里面有个ContextID,和ContextProperties 属性,表示上下问ID,和上下文属性的数组 |
CurrentThread |
获取当前正在运行的线程。 |
ExecutionContext |
获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 (没用到过,不清楚) |
IsAlive |
获取一个值,该值指示当前线程的执行状态。 |
IsBackground |
获取或设置一个值,该值指示某个线程是否为后台线程。 |
IsThreadPoolThread |
获取一个值,该值指示线程是否属于托管线程池。 |
ManagedThreadId |
获取当前托管线程的唯一标识符。 |
Name |
获取或设置线程的名称。 |
Priority |
获取或设置一个值,该值指示线程的调度优先级。 |
ThreadState |
获取一个值,该值包含当前线程的状态。(Unstarted、Sleeping、Running) |
1.2线程的优先级别
成员名称 |
说明 |
Lowest |
可以将 Thread 安排在具有任何其他优先级的线程之后。 |
BelowNormal |
可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。 |
Normal |
默认选择。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前。 |
AboveNormal |
可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。 |
Highest |
可以将 Thread 安排在具有任何其他优先级的线程之前。 |
1.3System.Threading.Thread的方法
方法名称 |
说明 |
Abort() |
终止本线程。 |
GetDomain() |
返回当前线程正在其中运行的当前域。 |
GetDomainId() |
返回当前线程正在其中运行的当前域Id。 |
Interrupt() |
中断处于 WaitSleepJoin 线程状态的线程。 |
Join() |
已重载。 阻塞调用线程,直到某个线程终止时为止。 |
Resume() |
继续运行已挂起的线程。 |
Start() |
执行本线程。 |
Suspend() |
挂起当前线程,如果当前线程已属于挂起状态则此不起作用 |
Sleep() |
把正在运行的线程挂起一段时间。 |
2.1System.Threading 命名空间
类 |
说明 |
AutoResetEvent |
通知正在等待的线程已发生事件。无法继承此类。 |
ExecutionContext |
管理当前线程的执行上下文。无法继承此类。 |
Interlocked |
为多个线程共享的变量提供原子操作。 |
Monitor |
提供同步对对象的访问的机制。 |
Mutex |
一个同步基元,也可用于进程间同步。 |
Thread |
创建并控制线程,设置其优先级并获取其状态。 |
ThreadAbortException |
在对 Abort 方法进行调用时引发的异常。无法继承此类。 |
ThreadPool |
提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。 |
Timeout |
包含用于指定无限长的时间的常数。无法继承此类。 |
Timer |
提供以指定的时间间隔执行方法的机制。无法继承此类。 |
WaitHandle |
封装等待对共享资源的独占访问的操作系统特定的对象。 |
2.2System.Threading中的包含了下表中的多个常用委托
委托 |
说明 |
ContextCallback |
表示要在新上下文中调用的方法。 |
ParameterizedThreadStart |
表示在 Thread 上执行的方法。 |
ThreadExceptionEventHandler |
表示将要处理 Application 的 ThreadException 事件的方法。 |
ThreadStart |
表示在 Thread 上执行的方法。 |
TimerCallback |
表示处理来自 Timer 的调用的方法。 |
WaitCallback |
表示线程池线程要执行的回调方法。 |
WaitOrTimerCallback |
表示当 WaitHandle 超时或终止时要调用的方法。 |
线程的管理方式
通过ThreadStart来创建一个新线程是最直接的方法,但这样创建出来的线程比较难管理,如果创建过多的线程反而会让系统的性能下载。
有见及此,.NET为线程管理专门设置了一个CLR线程池,使用CLR线程池系统可以更合理地管理线程的使用。所有请求的服务都能运行于线程池中,当运行结束时线程便会回归到线程池。通过设置,能控制线程池的最大线程数量,在请求超出线程最大值时,线程池能按照操作的优先级别来执行,让部分操作处于等待状态,待有线程回归时再执行操作。
三.线程的使用
1. 使用ThreadStart委托
应用于:最简单的方式,只需要传入一个调用的方法就行。
Thread thread = new Thread(new ThreadStart(message.ShowMessage));
// message.ShowMessage被调用的方法
thread.Start();
2. 使用ParameterizedThreadStart委托
应用于:需要带参数的线程。参数为object
Thread thread = new Thread(new ParameterizedThreadStart(message.ShowMessage));
//先在里面传入一个要被调用的message.ShowMessage方法
Person person = new Person();
person.Name = "Jack";
person.Age = 21;
thread.Start(person);
//启动时,把要传递的参数传过去,这里是传一个定义好的Person对象。
3. 通过QueueUserWorkItem启动工作者线程
重构两个方法:
一为 ThreadPool.QueueUserWorkItem(WaitCallback)
二为 ThreadPool.QueueUserWorkItem(WaitCallback,Object)
区别:一个是不带参数,一个是带参数。
ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback),"Hello Elva");
//后面的传值"Hello Elva",回到state上通过转型获得
static void AsyncCallback(object state)
{
Thread.Sleep(200);
string data = (string)state;
Console.WriteLine("Async thread do work!\n"+data);
}
QueueUserWorkItem的限制
通过ThreadPool.QueueUserWorkItem启动工作者线程虽然是方便,但WaitCallback委托指向的必须是一个带有Object参数的无返回值方法,这无疑是一种限制。若方法需要有返回值,或者带有多个参数,这将多费周折。
在面对回调方法有返回值,带多个参数,需要使用到委托。
4. 委托类
IAsyncResult介绍
public interface IAsyncResult
{
object AsyncState {get;} //获取用户定义的对象,它限定或包含关于异步操作的信息。
WailHandle AsyncWaitHandle {get;} //获取用于等待异步操作完成的 WaitHandle。
bool CompletedSynchronously {get;} //获取异步操作是否同步完成的指示。
bool IsCompleted {get;} //获取异步操作是否已完成的指示。
}
4.1无参数,无回调函数情况。
1.先要定义一个委托
delegate string MyDelegate(string name);
2. 建立委托
MyDelegate myDelegate = new MyDelegate(Hello);
3.启动异步调用委托
IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);
4.定义委托调用的方法
static string Hello(string nam)
{
return "Hello " + name;
}
5. 判断异步委托是否完成。
while (!result.IsCompleted)
{
Thread.Sleep(200); //虚拟操作
Console.WriteLine("Main thead do work!");
}
6.异步委托结束,获得异步返回值
string data=myDelegate.EndInvoke(result);
除此以外,也可以使用WailHandle完成同样的工作,WaitHandle里面包含有一个方法WaitOne(int timeout),它可以判断委托是否完成工作,在工作未完成前主线程可以继续其他工作。运行下面代码可得到与使用 IAsyncResult.IsCompleted 同样的结果,而且更简单方便 。
他们的使用区别
while (!result.AsyncWaitHandle.WaitOne(200))
==
while (!result.IsCompleted)
{
Thread.Sleep(200); //虚拟操作
}
当要监视多个运行对象的时候,使用IAsyncResult.WaitHandle.WaitOne可就派不上用场了。幸好.NET为WaitHandle准备了另外两个静态方法:WaitAny(waitHandle[], int)与WaitAll (waitHandle[] , int)。其中WaitAll在等待所有waitHandle完成后再返回一个bool值。而WaitAny是等待其中一个waitHandle完成后就返回一个int,这个int是代表已完成waitHandle在waitHandle[]中的数组索引。
使用方法:
WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle,........ };
while (!WaitHandle.WaitAll(waitHandleList,200))
{
Console.WriteLine("Main thead do work!");
}
4.2带有回调函数以及带参数的异步委托
1.定义一个参数对象Person
public class Person
{
public string Name;
public string Age;
}
2.定义一个委托:
delegate string MyDelegate(string name);
3.实例化一个委托,传递委托要调用的函数Hello
MyDelegate myDelegate=new MyDelegate(Hello);
4.实例化一个传递参数Person
Person person=new Person();
person.Name=”Elva”;
person.Age=27;
5.异步调用委托,输入参数对象person、获得计算结果。
myDelegate.BeginInvoke(“Leslive”,new AsyncCallback(Completed),person);
6.定义委托要调用的Hello函数
static string Helle(string name){…};
7.定义回调函数
static void Completed(IAsyncResult result)
{
AsyncResult _result=(AsyncResult)result;
MyDelegate myDelegate=(MyDelegate)_result.AsyncDelegate;
string date=myDelegate.EndInvoke(_result);
Person person=(Person)result.AsyncState;
string message=person.Name+”’s age is”+person.Age.ToString();
Console.WriteLine(data+”\n”+message);
}
ThreadPool相比Thread来说具备了很多优势
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且还不能控制线程池中线程的开始、挂起、和中止。
任务的学习:
一:Task的优势
ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存一些使用上的不方便。比如:
1: ThreadPool不支持线程的取消、完成、失败通知等交互性操作;
2: ThreadPool不支持线程执行的先后次序;而任务支持
以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在,FCL中提供了一个功能更强大的概念:Task。
Task 是在Threading类库里面抽象出来的一个类Threading.Tasks;
下面通过例子来说明任务的用法: