本文参考了官方
Dev Guide文档,简单介绍Android下的affinities和任务(task)。
Activity和Task
task就好像是能包含很多activity的栈。 默认情况下,一个activity启动另外一个activity时,两个activity是放在同一个task栈中的,第二个activity压入第一个activity所在的task栈。当用户按下返回键时,第二个activity从栈中弹出,第一个activity又在当前屏幕显示。这样,从用户角度来看,这两个activity就好像是属于同一个应用程序的,即使第二个activity是属于另外一个应用程序的。当然,这是指默认情况下。 task栈包含的是activity的对象。如果一个activity有多个实例在运行,那么栈中保存的是每个实例的实体。栈中的activity不会重新排列,只有弹出和压入操作。 一个task中的所有activity都以整体的形式移动。整个task可以被移到前台或后台。打个比方,当前的task包含4个activity–当前activity下面有3个activity。当用户按下HOME键返回到程序启动器(application launcher)后,选择了一个新的应用程序(事实上是一个新的task),当前的task就被转移到后台,新的task中的根activity将被显示在屏幕上。过了一段时间,用户按返回键回到了程序启动器界面,选择了之前运行的程序(之前的task)。那个task,仍然包含着4个activity。当用户再次按下返回键时,屏幕不会显示之前留下的那个activity(之前的task的根activity),而显示当前activity从task栈中移出后栈顶的那个activity。 刚刚描述的行为是默认的activity和task的行为。有很多方法能够改变这种行为。activity和task之间的联系,以及task中的activity的行为可以通过intent中的标记以及在manifest中的<activity>元素的属性控制。其中,主要的Intent标记有:
- FLAG_ACTIVITY_NEW_TASK
- FLAG_ACTIVITY_CLEAR_TOP
- FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
- FLAG_ACTIVITY_SINGLE_TOP
主要的<activity>属性有:
- taskAffinity
- launchMode
- allowTaskReparenting
- clearTaskOnLaunch
- alwaysRetainTaskState
- finishOnTaskLaunch
默认情况下,一个应用程序中的所有activity都有一个affinity–这让它们属性同一个task。然而,每个activity可以通过<activity>中的taskAffinity属性设置单独的affinity。不同应用程序中的activity可以共享同一个affinity,同一个应用程序中的不同activity也可以设置成不同的affinity。affinity属性决定了:启动activity的Intent对象需包含FLAG_ACTIVITY_NEW_TASK标记,activity的allowTaskReparenting被认为是设置成true。
FLAG_ACTIVITY_NEW_TASK标记
当传递给startActivity()的Intent对象包含FLAG_ACTIVITY_NEW_TASK标记时,系统会为需要启动的activity寻找与当前activity不同的task。如果要启动的activity的affinity属性与当前所有的task的affinity属性都不相同,系统会新建一个带那个affinity属性的task,并将要启动的activity压到新建的task栈中;否则将activity压入那个affinity属性相同的栈中。
allowTaskReparenting属性
如果一个activity的allowTaskReparenting属性为true,那么它可以从一个task(TASK1)移到另外一个有相同affinity的task(TASK2)中(TASK2带到前台时)。
如果一个.apk文件从用户角度来看包含了多个“应用程序”,你可能需要对那些activity赋不同的affinity值。
运行模式
activity的launchMode属性可以有四种值:
- “
standard
” (默认) - “
singleTop
“ - “
singleTask
“ - “
singleInstance
“
这4种模式可以按4种分类来区分,以下假设位于task1中的activity1启动activity2:
模式分类 | 包容activity2的task | 一个activity是否允许有多个实例 | activity是否允许有其它activity共存于一个task | 对于新的intent,是否总是实例化activity对象 |
standard | 如果不包含FLAG_ACTIVITY_NEW_TASK标记,则activity2放入task1,否则按前面讲述的规则为activity2选择task | 可被多次实例化,同一个task的不同的实例可位于不同的task中,每个task也可包含多个实例 | 允许 | 是的。当接收到新的intent时,总是会生成新的activity对象。 |
singleTop | 同standard | 同standard | 允许 | 已存在的activity对象,如果位于目标task的栈顶,则该activity被重用,如果它不位于栈顶,则会实例化新的activity对象 |
singleTask | 将activity2放到task1栈底 | 不能有多个实例。由于该模式下activity总是位于栈顶,所以actvity在同一个设备里至多只有一个实例 | 允许。singleTask模式的activity总是位于栈底位置。目标activity实例已存在时,如果该实例刚好位于task栈顶,则接收intent,否则到来的intent将会被丢弃,但这会导致目标activity所在的task被移到前台。 | |
singleInstance | 同singleTask | 同singleTask | 不允许与其它activity共存于一个task。如果activity1的运行在该模式下,则activity2一定与activity1位于不同的task | |
对于新到的intent,如果是由新创建的activity对象来接收,则用户可以通过返回键回到之前的activity;如果是由已存在的activity来接收,则用户无法通过返回键返回到接收intent之前的状态。
清空栈
当用户长时间离开task(当前task被转移到后台)时,系统会清除task中栈底activity外的所有activity。这样,当用户返回到task时,只留下那个task最初始的activity了。
这是默认的情况,<activity>中有些属性可以改变这种行为。
alwaysRetainTaskState属性
如果栈底activity的这个属性被设置为true,刚刚描述的情况就不会发生。task中的所有activity将被长时间保存。
clearTaskOnLaunch属性
如果栈底activity的这个属性被设置为true,一旦用户离开task,则task栈中的activity将被清空到只剩下栈底activity。这种情况刚好与alwaysRetainTaskState相反。即使用户只是短暂地离开,task也会返回到初始状态(只剩下栈底acitivty)。
finishOnTaskLaunch属性
这个属性与clearTaskOnLaunch相似,但它只对单独的activity操作,而不是整个task。它可以结束任何activity,包括栈底的activity。当它设置为true时,当前的activity只在当前会话期间作为task的一部分存在,当用户退出activity再返回时,它将不存在。
另外还有一种方法能将activity强行从stack中移出。如果intent对象包含FLAG_ACTIVITY_CLEAR_TOP标记,当目标task中已存在与接收该intent对象的activity类型相同的activity实例存在时,所有位于该activity对象上面的activity将被清空,这样接收该intent的activity就位于栈顶,可以响应到来的intent对象。如果目标activity的运行模式为standard,则目标activtiy也会被清空。因为当运行模式为standard时,总会创建新的activity对象来接收到来的intent对象。
FLAG_ACTIVITY_CLEAR_TOP标记常常和FLAG_ACTIVITY_NEW_TASK一起使用。用2个标记可以定位已存在的activity并让它处于可以响应intent的位置。
启动任务(Task)
Intent filter中有”android.intent.action.MAIN
” action和”android.intent.category.LAUNCHER
” category的activity将被标记为task的入口。带有这两个标记的activity将会显示在应用程序启动器(application launcher)中。
第二个比较重要的点是,用户必须能够离开task并在之后返回。因为这个原因,singleTask和singleInstance这两种运行模式只能应用于含有MAIN和LAUNCHER过滤器的activity。打个比方,如果不包含带MAIN和LAUNCHER过滤器,某个activity运行了一个singleTask模式的activity,初始化了一个新的task,当用户按下HOME键时,那个activity就被主屏幕“挡住”了,用户再也无法返回到那个activity。
类似的情况在FLAG_ACTIVITY_NEW_TASK标记上也会出现。如果这个标记会新建一个task,当用户按下HOME键时,必须有一种方式能够让用户返回到那个activity。有些东西(比如notification manager)总是要求在外部task中启动activity,在传递给startActivity的intent中总是包含FLAG_ACTIVITY_NEW_TASK标记。
对于那种不希望用户离开之后再返回activity的情况,可将finishOnTaskLaunch属性设置为true。
- 维护自由锁(InterLocked)实现同步
- 监视器(Monitor)和互斥锁(lock)
- 读写锁(ReadWriteLock)
- 系统内核对象
- 互斥(Mutex), 信号量(Semaphore), 事件(AutoResetEvent/ManualResetEvent)
- 线程池
InterLocked
为多个线程共享的变量提供原子操作。
此类的方法可以防止可能在下列情况发生的错误:计划程序在某个线程正在更新可由其他线程访问的变量时切换上下文;或者当两个线程在不同的处理器上并发执行时。此类的成员不引发异常。
Increment 和 Decrement 方法递增或递减变量并将结果值存储在单个操作中。在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
- 将实例变量中的值加载到寄存器中。
- 增加或减少该值。
- 在实例变量中存储该值。
如果不使用 Increment 和 Decrement,线程会在执行完前两个步骤后被抢先。然后由另一个线程执行所有三个步骤。当第一个线程重新开始执行时,它覆盖实例变量中的值,造成第二个线程执行增减操作的结果丢失。
Exchange 方法自动交换指定变量的值。CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。比较和交换操作按原子操作执行。下面的代码示例说明线程安全资源锁定机制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
using
System;
using
System.Threading;
namespace
InterlockedExchange_Example
{
class
MyInterlockedExchangeExampleClass
{
private
static
int
usingResource = 0;
private
static
Object currentMso;
private
static
Object globalMso =
new
Object();
private
const
int
numThreadIterations = 5;
private
const
int
numThreads = 10;
static
void
Main()
{
Thread myThread;
Random rnd =
new
Random();
for
(
int
i = 0; i < numThreads; i++)
{
myThread =
new
Thread(
new
ThreadStart(MyThreadProc));
myThread.Name = String.Format("Thread{0}", i + 1);
Thread.Sleep(rnd.Next(0, 1000));
myThread.Start();
}
}
private
static
void
MyThreadProc()
{
for
(
int
i = 0; i < numThreadIterations; i++)
{
UseResource();
Thread.Sleep(1000);
}
}
static
bool
UseResource()
{
if
(0 == Interlocked.Exchange(
ref
usingResource, 1))
{
Console.WriteLine("{0} acquired the
lock
", Thread.CurrentThread.Name);
Thread.Sleep(500);
Console.WriteLine("{0} exiting
lock
", Thread.CurrentThread.Name);
Interlocked.Exchange(
ref
usingResource, 0);
return
true
;
}
else
{
Console.WriteLine(" {0} was denied the
lock
", Thread.CurrentThread.Name);
return
false
;
}
}
}
}
|
监视器(Monitor)和互斥锁(lock)
Monitor类
提供同步对对象的访问的机制。Monitor 类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。
Monitor 对象通过使用 Monitor.Enter、Monitor.TryEnter 和Monitor.Exit 方法对特定对象获取锁和释放锁来公开同步访问代码区域的能力。在对代码区域获取锁后,就可以使用Monitor.Wait、Monitor.Pulse 和 Monitor.PulseAll 方法了。如果锁被暂挂,则 Wait 释放该锁并等待通知。当 Wait 接到通知后,它将返回并再次获取该锁。Pulse 和 PulseAll 都会发出信号以便等待队列中的下一个线程继续执行。
Monitor 将锁定对象(即引用类型),而非值类型。尽管可以向 Enter 和Exit 传递值类型,但对于每次调用它都是分别装箱的。因为每次调用都创建一个独立的对象,所以 Enter 永远不会阻止,而且它要保护的代码并没有真正同步。另外,传递给 Exit 的对象不同于传递给 Enter 的对象,所以Monitor 将引发 SynchronizationLockException,并显示以下消息:“从不同步的代码块中调用了对象同步方法。”下面的示例演示这些问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
int
x;
Monitor.Enter(x);
try
{
}
finally
{
Monitor.Exit(x);
}
|
尽管您可以如下面的示例所示,在调用 Enter 和 Exit 之前将值类型变量装箱,并将同一个装箱的对象传递给这两个方法,但这样做并没有什么特别的用处。对变量的更改不能在装箱的变量中体现出来,也没有办法更改已装箱的变量的值。
注意到 Monitor 和 WaitHandle 对象在使用上的区别是非常重要的。Monitor 对象是完全托管、完全可移植的,并且在操作系统资源要求方面可能更为有效。WaitHandle 对象表示操作系统可等待对象,对于在托管和非托管代码之间进行同步非常有用,并公开一些高级操作系统功能(如同时等待许多对象的能力)。
Monitor.Wait (Object) 释放对象上的锁并阻塞(原文为阻止)当前线程,直到它重新获取该锁。
Monitor.Wait (Object, Int32) 释放对象上的锁并阻塞(原文为阻止)当前线程,直到它重新获取该锁。如果指定的超时间隔已过,则线程进入就绪队列。
对于Monitor.Wait(obj),我的理解是:将当前线程放到obj对象的等待队列中,阻塞当前线程,直到另外一个线程调用Monitor.Pulse(obj)。而含有一个整型参数的Wait则限定了一个时限–如果在时限内,当前线程获得了锁(有其它线程调用了Monitor.Pulse(obj)),则Wait返回true;否则,在时限内当前线程还未获得对象的锁,则当前线程进入obj的就绪队列,Wait返回false。
Monitor.Pulse(obj)和Monitor.PulseAll(obj)用于通知等待队列中的线程锁定对象状态的更改。前者只通知等待队列中的第一个线程,后者通知等待队列中的所有线程,即将拥有当前对象的锁的线程全部移到就绪队列中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
using
System;
using
System.Threading;
namespace
PulseAll
{
class
Program
{
static
Object locker =
new
Object();
static
void
Method1()
{
Console.WriteLine("Enter Method1");
lock
(locker)
{
Console.WriteLine("
in
method1");
Monitor.PulseAll(locker);
}
}
static
void
Method2()
{
Console.WriteLine("Enter Method2");
lock
(locker)
{
Monitor.Wait(locker);
Console.WriteLine("
in
method2");
}
}
static
void
Method3()
{
Console.WriteLine("Enter Method3");
lock
(locker)
{
Monitor.Wait(locker);
Console.WriteLine("
in
method3");
}
}
static
void
Main(
string
[] args)
{
Thread thread1 =
new
Thread(
new
ThreadStart(Method1));
Thread thread2 =
new
Thread(
new
ThreadStart(Method2));
Thread thread3 =
new
Thread(
new
ThreadStart(Method3));
thread2.Start();
thread3.Start();
thread1.Start();
thread2.Join();
thread3.Join();
thread1.Join();
Console.WriteLine("OK");
Console.ReadLine();
}
}
}
|
上述代码的运行结果:
但是上述代码并没有证明Wait方法释放了锁。
lock语句
lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。 此语句的形式如下:
Object thisLock = new Object();
lock (thisLock)
{
// Critical code section.
}
lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
线程处理(C# 编程指南) 这节讨论了线程处理。
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)、lock (typeof (MyType)) 和 lock (“myLock”) 违反此准则:
- 如果实例可以被公共访问,将出现 lock (this) 问题。
- 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
- 由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock(“myLock”) 问题。
最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。
按照上面的解释,使用lock(this)往往不能达到预期的目的。假设有两个线程t1和t2,都在内部生成了某个类C的对象,分别叫obj1和obj2。如果在类C中使用lock(this)标记临界区,那么当t1访问临界区时,并不能阻止t2访问该临界区。因为t1中的lock锁定的是obj1对象,obj2并未锁定,而t2中的lock锁定的是obj2对象。正如MSDN中所说,最佳的方法是锁定对两个线程都可见的私有对象,或者类的私有静态对象。
ReadWriteLock
暂略,具体查看MSDN
系统内核对象
MUTEX
当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。
可以使用 WaitHandle.WaitOne 方法请求互斥体的所属权。拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行。但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。Mutex 类强制线程标识,因此互斥体只能由获得它的线程释放。相反,Semaphore 类不强制线程标识。
如果线程在拥有互斥体时终止,则称此互斥体被放弃。将此 mutex 的状态设置为收到信号,下一个等待线程将获得所有权。从 .NET Framework 2.0 版开始,在获取被放弃 mutex 的下一个线程中将引发AbandonedMutexException。在 .NET Framework 2.0 版之前,这样不会引发任何异常。
对于系统范围的 mutex,被放弃的 mutex 可能表明应用程序已突然终止(例如,通过使用 Windows 任务管理器)。
Mutex 有两种类型:未命名的局部 mutex 和已命名的系统 mutex。局部互斥体仅存在于您的进程内。您的进程中任何引用表示 mutex 的 Mutex 对象的线程都可以使用它。每个未命名的 Mutex 对象都表示一个单独的局部 mutex。
已命名的系统互斥体在整个操作系统中都可见,可用于同步进程活动。您可以使用接受名称的构造函数创建表示已命名系统 mutex 的 Mutex 对象。同时也可以创建操作系统对象,或者它在创建 Mutex 对象之前就已存在。您可以创建多个 Mutex 对象来表示同一个已命名的系统互斥体,也可以使用OpenExisting 方法打开现有的已命名系统互斥体。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
using
System;
using
System.Threading;
class
Test
{
private
static
Mutex mut =
new
Mutex();
private
const
int
numIterations = 1;
private
const
int
numThreads = 3;
static
void
Main()
{
for
(
int
i = 0; i < numThreads; i++)
{
Thread myThread =
new
Thread(
new
ThreadStart(MyThreadProc));
myThread.Name = String.Format(
"Thread{0}"
, i + 1);
myThread.Start();
}
}
private
static
void
MyThreadProc()
{
for
(
int
i = 0; i < numIterations; i++)
{
UseResource();
}
}
private
static
void
UseResource()
{
mut.WaitOne();
Console.WriteLine(
"{0} has entered the protected area"
,
Thread.CurrentThread.Name);
Thread.Sleep(500);
Console.WriteLine(
"{0} is leaving the protected arearn"
,
Thread.CurrentThread.Name);
mut.ReleaseMutex();
}
}
|
SEMAPHORE
使用 Semaphore 类可控制对资源池的访问。线程通过调用 WaitOne 方法(从WaitHandle 类继承)进入信号量,并通过调用 Release 方法释放信号量。
信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。
被阻止的线程并不一定按特定的顺序(如 FIFO 或 LIFO)进入信号量。
线程可通过重复调用 WaitOne 方法多次进入信号量。为释放这些入口中的部分或全部,线程可多次调用无参数的 Release()()() 方法重载,也可以调用 Release(Int32) 方法重载来指定要释放的入口数。
Semaphore 类不对 WaitOne 或 Release 调用强制线程标识。程序员负责确保线程释放信号量的次数不能太多。例如,假定信号量的最大计数为 2,并且线程 A 和线程 B 同时进入信号量。如果线程 B 中的编程错误导致它两次调用 Release,则两次调用都成功。这样,信号量的计数已满,当线程 A 最终调用 Release 时便会引发SemaphoreFullException。
信号量分为两种类型:局部信号量和已命名的系统信号量。如果您使用接受名称的构造函数创建 Semaphore 对象,则该对象与具有该名称的操作系统信号量关联。已命名的系统信号量在整个操作系统中都可见,可用于同步进程活动。您可以创建多个 Semaphore 对象来表示同一个已命名的系统信号量,也可以使用 OpenExisting 方法打开现有的已命名系统信号量。
局部信号量仅存在于您的进程内。您的进程中任何引用局部 Semaphore 对象的线程都可以使用它。每个 Semaphore 对象都是一个单独的局部信号量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
using
System;
using
System.Threading;
public
class
Example
{
private
static
Semaphore _pool;
private
static
int
_padding;
public
static
void
Main()
{
_pool =
new
Semaphore(0, 3);
for
(
int
i = 1; i <= 5; i++)
{
Thread t =
new
Thread(
new
ParameterizedThreadStart(Worker));
t.Start(i);
}
Thread.Sleep(500);
Console.WriteLine(
"Main thread calls Release(3)."
);
_pool.Release(3);
Console.WriteLine(
"Main thread exits."
);
}
private
static
void
Worker(
object
num)
{
Console.WriteLine(
"Thread {0} begins "
+
"and waits for the semaphore."
, num);
_pool.WaitOne();
int
padding = Interlocked.Add(
ref
_padding, 100);
Console.WriteLine(
"Thread {0} enters the semaphore."
, num);
Thread.Sleep(1000 + padding);
Console.WriteLine(
"Thread {0} releases the semaphore."
, num);
Console.WriteLine(
"Thread {0} previous semaphore count: {1}"
,
num, _pool.Release());
}
}
|
Semaphore.Release()返回的是释放之前信号量的大小。运行结果:
AUTORESETEVENT
AutoResetEvent allows threads to communicate with each other by signaling. Typically, this communication concerns a resource to which threads need exclusive access.
A thread waits for a signal by calling WaitOne on the AutoResetEvent. If the AutoResetEvent is in the nonsignaled state, the thread blocks, waiting for the thread that currently controls the resource to signal that the resource is available by calling Set.
Calling Set signals AutoResetEvent to release a waiting thread.AutoResetEvent remains signaled until a single waiting thread is released, and then automatically returns to the non-signaled state. If no threads are waiting, the state remains signaled indefinitely.
You can control the initial state of an AutoResetEvent by passing a Boolean value to the constructor, true if the initial state is signaled andfalse otherwise.
AutoResetEvent can also be used with the staticWaitAll and WaitAnymethods.
也就是说,当AutoResetEvent处于未通知状态(nonsignaled state)时,调用AutoResetEvent.WaitOne()的线程将处于阻塞状态,直到有其它线程调用AutoResetEvent.Set()。调用AutoResetEvent对象的Set方法将释放一个等待线程(注意是1个),AutoResetEvent保持通知状态(signaled),直到有一个线程被释放,AutoResetEvent自动返回non-signaled状态。如果没有等待线程,则AutoResetEvent对象将一直保持通知状态(remains signaled indefinitely)。
MANUALRESETEVENT
ManualResetEvent allows threads to communicate with each other by signaling. Typically, this communication concerns a task which one thread must complete before other threads can proceed.
When a thread begins an activity that must complete before other threads proceed, it calls Reset to put ManualResetEvent in the non-signaled state. This thread can be thought of as controlling theManualResetEvent. Threads that call WaitOne on theManualResetEvent will block, awaiting the signal. When the controlling thread completes the activity, it calls Set to signal that the waiting threads can proceed. All waiting threads are released.
Once it has been signaled, ManualResetEvent remains signaled until it is manually reset. That is, calls to WaitOne return immediately.
You can control the initial state of a ManualResetEvent by passing a Boolean value to the constructor, true if the initial state is signaled andfalse otherwise.
ManualResetEvent can also be used with the staticWaitAll andWaitAny methods.
ManualResetEvent与AutoResetEvent有相同的作用。不同的是,当调用它的Set方法的时候,所有因调用WaitOne而阻塞的线程都将被释放(唤醒),它的signaled状态一直被保持,直到人为地将其重置–调用WaitOne,WaitOne方法在调用之后立即返回。例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
using
System;
using
System.Threading;
class
CalculateTest
{
static
void
Main()
{
Calculate calc =
new
Calculate();
Console.WriteLine(
"Result = {0}."
,
calc.Result(234).ToString());
Console.WriteLine(
"Result = {0}."
,
calc.Result(55).ToString());
Console.ReadLine();
}
}
class
Calculate
{
double
baseNumber, firstTerm, secondTerm, thirdTerm;
AutoResetEvent[] autoEvents;
ManualResetEvent manualEvent;
Random randomGenerator;
public
Calculate()
{
autoEvents =
new
AutoResetEvent[]
{
new
AutoResetEvent(
false
),
new
AutoResetEvent(
false
),
new
AutoResetEvent(
false
)
};
manualEvent =
new
ManualResetEvent(
false
);
}
void
CalculateBase(
object
stateInfo)
{
baseNumber = randomGenerator.NextDouble();
Console.WriteLine(
"in CalculateBase"
);
manualEvent.Set();
}
void
CalculateFirstTerm(
object
stateInfo)
{
double
preCalc = randomGenerator.NextDouble();
Console.WriteLine(
"before WaitOne in CalculateFirstTerm"
);
manualEvent.WaitOne();
Console.WriteLine(
"after WaitOne in CalculateFirstTerm"
);
firstTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
autoEvents[0].Set();
}
void
CalculateSecondTerm(
object
stateInfo)
{
double
preCalc = randomGenerator.NextDouble();
Console.WriteLine(
"before WaitOne in CalculateSecondTerm"
);
manualEvent.WaitOne();
Console.WriteLine(
"after WaitOne in CalculateSecondTerm"
);
secondTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
autoEvents[1].Set();
}
void
CalculateThirdTerm(
object
stateInfo)
{
double
preCalc = randomGenerator.NextDouble();
Console.WriteLine(
"before WaitOne in CalculateThirdTerm"
);
manualEvent.WaitOne();
Console.WriteLine(
"after WaitOne in CalculateThirdTerm"
);
thirdTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
autoEvents[2].Set();
}
public
double
Result(
int
seed)
{
randomGenerator =
new
Random(seed);
ThreadPool.QueueUserWorkItem(
new
WaitCallback(CalculateBase));
ThreadPool.QueueUserWorkItem(
new
WaitCallback(CalculateFirstTerm));
ThreadPool.QueueUserWorkItem(
new
WaitCallback(CalculateSecondTerm));
ThreadPool.QueueUserWorkItem(
new
WaitCallback(CalculateThirdTerm));
WaitHandle.WaitAll(autoEvents);
manualEvent.Reset();
return
firstTerm + secondTerm + thirdTerm;
}
}
|
上面一个例子打算先运行CalculateBase(),再让3个不同的线程同时运行CalculateFirstTerm(),CalculateSecondTerm(),CalculateThirdTerm()。ManualResetEvent起到了开关的作用:当CalculateBase()运行结束后,manualEvent发一个信号,告诉所有在等待的线程“我这里计算好啦,请你们继续计算”。其它 3个线程在收到信号后分别开始自己的计算,并在计算结束后通过AutoResetEvent对象发信号,向主线程报告自己的计算任务完成了。主线程在收到所有线程的信号后(通过WaitHandle.WaitAll(autoEvents)实现),将3个线程的计算结果相加,返回给Main方法。程序运行结果:
线程池(THREADPOOL类)
有些情况下,线程可能将长时间处于挂起状态,只需要周期性地“醒”过来做某些事情。而线程的创建和销毁都有一定的开销,使用线程池能够让你更高效地使用线程。
.Net 中,ThreadPool里的线程都是后台线程(IsBackground=true),这意味着,前台线程终止以后,线程池中的线程也将被终止。一个进程只能有一个ThreadPool,默认情况下,对每个可用的处理器,每个ThreadPool最大有250个工作线程和1000个IO完成线程(I/O completion threads,啥意思?),最小空闲线程的数目与处理器数目相当。这2个值可以通过SetMaxThreads和SetMinThreads来重新设定。
When the thread pool reuses a thread, it does not clear the data in thread local storage or in fields that are marked with the ThreadStaticAttributeattribute. Therefore, data that is placed in thread local storage by one method can be exposed to any other method that is executed by the same thread pool thread. A method that accesses a field that is marked with theThreadStaticAttribute attribute could encounter different data depending on which thread pool thread executes it.
Starting with the .NET Framework version 2.0 Service Pack 1, the throughput of the thread pool is significantly improved for applications that make heavy use of small thread pool tasks. These applications will see improvements in three areas: queuing tasks, dispatching thread pool threads, and dispatching I/O completion threads. To use this functionality, your application should target the .NET Framework version 3.5. For more information, see .NET Framework 3.5 Architecture.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
using
System;
using
System.Threading;
public
class
Example {
public
static
void
Main() {
ThreadPool.QueueUserWorkItem(
new
WaitCallback(ThreadProc));
Console.WriteLine(
"Main thread does some work, then sleeps."
);
Thread.Sleep(1000);
Console.WriteLine(
"Main thread exits."
);
}
static
void
ThreadProc(Object stateInfo) {
Console.WriteLine(
"Hello from the thread pool."
);
}
}
|