`
gybin
  • 浏览: 272035 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Android下affinities和任务(task)

 
阅读更多

Android下affinities和任务(task)

本文参考了官方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。

Posted in Android Tagged activityaffinitiesAndroidtask 1 Reply Permalink

线程同步(.Net)

  • 维护自由锁(InterLocked)实现同步
  • 监视器(Monitor)和互斥锁(lock)
  • 读写锁(ReadWriteLock)
  • 系统内核对象
    • 互斥(Mutex), 信号量(Semaphore), 事件(AutoResetEvent/ManualResetEvent)
    • 线程池

InterLocked

为多个线程共享的变量提供原子操作。

此类的方法可以防止可能在下列情况发生的错误:计划程序在某个线程正在更新可由其他线程访问的变量时切换上下文;或者当两个线程在不同的处理器上并发执行时。此类的成员不引发异常。

Increment 和 Decrement 方法递增或递减变量并将结果值存储在单个操作中。在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:

  1. 将实例变量中的值加载到寄存器中。
  2. 增加或减少该值。
  3. 在实例变量中存储该值。

如果不使用 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
    {
        //0 for false, 1 for true.
        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 &lt; numThreads; i++)
            {
                myThread = new Thread(new ThreadStart(MyThreadProc));
                myThread.Name = String.Format(&quot;Thread{0}&quot;, i + 1);
 
                //Wait a random amount of time before starting next thread.
                Thread.Sleep(rnd.Next(0, 1000));
                myThread.Start();
            }
        }
 
        private static void MyThreadProc()
        {
            for(int i = 0; i &lt; numThreadIterations; i++)
            {
                UseResource();
 
                //Wait 1 second before next attempt.
                Thread.Sleep(1000);
            }
        }
 
        //A simple method that denies reentrancy.
        static bool UseResource()
        {
            //0 indicates that the method is not in use.
            if(0 == Interlocked.Exchange(ref usingResource, 1))
            {
                Console.WriteLine(&quot;{0} acquired the lock&quot;, Thread.CurrentThread.Name);
 
                //Code to access a resource that is not thread safe would go here.
 
                //Simulate some work
                Thread.Sleep(500);
 
                Console.WriteLine(&quot;{0} exiting lock&quot;, Thread.CurrentThread.Name);
 
                //Release the lock
                Interlocked.Exchange(ref usingResource, 0);
                return true;
            }
            else
            {
                Console.WriteLine(&quot;   {0} was denied the lock&quot;, Thread.CurrentThread.Name);
                return false;
            }
        }
 
    }
}

监视器(Monitor)和互斥锁(lock)

Monitor类

提供同步对对象的访问的机制。Monitor 类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。

Monitor 对象通过使用 Monitor.EnterMonitor.TryEnter 和Monitor.Exit 方法对特定对象获取锁和释放锁来公开同步访问代码区域的能力。在对代码区域获取锁后,就可以使用Monitor.WaitMonitor.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;
// The next line creates a generic object containing the value of
// x each time the code is executed, so that Enter never blocks.
Monitor.Enter(x);
try {
    // Code that needs to be protected by the monitor.
}
finally {
    // Always use Finally to ensure that you exit the Monitor.
    // The following line creates another object containing
    // the value of x, and throws SynchronizationLockException
    // because the two objects do not match.
    Monitor.Exit(x);
}

尽管您可以如下面的示例所示,在调用 Enter 和 Exit 之前将值类型变量装箱,并将同一个装箱的对象传递给这两个方法,但这样做并没有什么特别的用处。对变量的更改不能在装箱的变量中体现出来,也没有办法更改已装箱的变量的值。

1
private Object o = x;

注意到 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(&quot;Enter Method1&quot;);
            lock (locker)
            {
                Console.WriteLine(&quot;in method1&quot;);
                Monitor.PulseAll(locker);
            }
        }
        static void Method2()
        {
            Console.WriteLine(&quot;Enter Method2&quot;);
            lock (locker)
            {
                Monitor.Wait(locker);
                Console.WriteLine(&quot;in method2&quot;);
            }
        }
        static void Method3()
        {
            Console.WriteLine(&quot;Enter Method3&quot;);
            lock (locker)
            {
                Monitor.Wait(locker);
                Console.WriteLine(&quot;in method3&quot;);
            }
        }
        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(&quot;OK&quot;);
            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
// This example shows how a Mutex is used to synchronize access
// to a protected resource. Unlike Monitor, Mutex can be used with
// WaitHandle.WaitAll and WaitAny, and can be passed across
// AppDomain boundaries.
 
using System;
using System.Threading;
 
class Test
{
    // Create a new Mutex. The creating thread does not own the
    // Mutex.
    private static Mutex mut = new Mutex();
    private const int numIterations = 1;
    private const int numThreads = 3;
 
    static void Main()
    {
        // Create the threads that will use the protected resource.
        for(int i = 0; i < numThreads; i++)
        {
            Thread myThread = new Thread(new ThreadStart(MyThreadProc));
            myThread.Name = String.Format("Thread{0}", i + 1);
            myThread.Start();
        }
 
        // The main thread exits, but the application continues to
        // run until all foreground threads have exited.
    }
 
    private static void MyThreadProc()
    {
        for(int i = 0; i < numIterations; i++)
        {
            UseResource();
        }
    }
 
    // This method represents a resource that must be synchronized
    // so that only one thread at a time can enter.
    private static void UseResource()
    {
        // Wait until it is safe to enter.
        mut.WaitOne();
 
        Console.WriteLine("{0} has entered the protected area",
            Thread.CurrentThread.Name);
 
        // Place code to access non-reentrant resources here.
 
        // Simulate some work.
        Thread.Sleep(500);
 
        Console.WriteLine("{0} is leaving the protected arearn",
            Thread.CurrentThread.Name);
 
        // Release the Mutex.
        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
{
    // A semaphore that simulates a limited resource pool.
    //
    private static Semaphore _pool;
 
    // A padding interval to make the output more orderly.
    private static int _padding;
 
    public static void Main()
    {
        // Create a semaphore that can satisfy up to three
        // concurrent requests. Use an initial count of zero,
        // so that the entire semaphore count is initially
        // owned by the main program thread.
        //
        _pool = new Semaphore(0, 3);
 
        // Create and start five numbered threads.
        //
        for(int i = 1; i <= 5; i++)
        {
            Thread t = new Thread(new ParameterizedThreadStart(Worker));
 
            // Start the thread, passing the number.
            //
            t.Start(i);
        }
 
        // Wait for half a second, to allow all the
        // threads to start and to block on the semaphore.
        //
        Thread.Sleep(500);
 
        // The main thread starts out holding the entire
        // semaphore count. Calling Release(3) brings the
        // semaphore count back to its maximum value, and
        // allows the waiting threads to enter the semaphore,
        // up to three at a time.
        //
        Console.WriteLine("Main thread calls Release(3).");
        _pool.Release(3);
 
        Console.WriteLine("Main thread exits.");
    }
 
    private static void Worker(object num)
    {
        // Each worker thread begins by requesting the
        // semaphore.
        Console.WriteLine("Thread {0} begins " +
            "and waits for the semaphore.", num);
        _pool.WaitOne();
 
        // A padding interval to make the output more orderly.
        int padding = Interlocked.Add(ref _padding, 100);
 
        Console.WriteLine("Thread {0} enters the semaphore.", num);
 
        // The thread's "work" consists of sleeping for
        // about a second. Each thread "works" a little
        // longer, just to make the output more orderly.
        //
        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;
 
    // Generate random numbers to simulate the actual calculations.
    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");
        // Signal that baseNumber is ready.
        manualEvent.Set();
    }
 
    // The following CalculateX methods all perform the same
    // series of steps as commented in CalculateFirstTerm.
 
    void CalculateFirstTerm(object stateInfo)
    {
        // Perform a precalculation.
        double preCalc = randomGenerator.NextDouble();
        Console.WriteLine("before WaitOne in CalculateFirstTerm");
        // Wait for baseNumber to be calculated.
        manualEvent.WaitOne();
        Console.WriteLine("after WaitOne in CalculateFirstTerm");
        // Calculate the first term from preCalc and baseNumber.
        firstTerm = preCalc * baseNumber *
            randomGenerator.NextDouble();
 
        // Signal that the calculation is finished.
        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);
 
        // Simultaneously calculate the terms.
        ThreadPool.QueueUserWorkItem(
            new WaitCallback(CalculateBase));
        ThreadPool.QueueUserWorkItem(
            new WaitCallback(CalculateFirstTerm));
        ThreadPool.QueueUserWorkItem(
            new WaitCallback(CalculateSecondTerm));
        ThreadPool.QueueUserWorkItem(
            new WaitCallback(CalculateThirdTerm));
 
        // Wait for all of the terms to be calculated.
        WaitHandle.WaitAll(autoEvents);
 
        // Reset the wait handle for the next calculation.
        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() {
        // Queue the task.
        ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
 
        Console.WriteLine("Main thread does some work, then sleeps.");
        // If you comment out the Sleep, the main thread exits before
        // the thread pool task runs.  The thread pool uses background
        // threads, which do not keep the application running.  (This
        // is a simple example of a race condition.)
        Thread.Sleep(1000);
 
        Console.WriteLine("Main thread exits.");
    }
 
    // This thread procedure performs the task.
    static void ThreadProc(Object stateInfo) {
        // No state object was passed to QueueUserWorkItem, so
        // stateInfo is null.
        Console.WriteLine("Hello from the thread pool.");
    }
}

Posted in .NET面试 Tagged 多线程 Leave a reply Permalink

分享到:
评论

相关推荐

    Android 应用程序模块 应用, 任务, 进程, 和线程--千锋培训

    在Android应用开发中,理解应用程序模块、任务(Tasks)、进程(Processes)和线程(Threads)的概念至关重要。这些组件是构建高效、响应且资源管理良好的应用程序的基础。 1. **应用程序模块(Application Modules...

    android编程 android编程入门 android开发 android软件

    Android编程入门是学习Android开发的基础,它涉及到许多关键概念和技术,包括应用程序的组成元素、Activity与Task管理、进程和线程以及元素的生命周期。 一、应用程序的组成元素 1. **Activity元素**:Activity是...

    Android学习笔记-Activity篇

    2. 定义launch模式:在AndroidManifest.xml中,可以通过`android:launchMode`属性定义Activity的启动模式,如标准模式(standard)、单实例模式(singleInstance)、单任务模式(singleTask)和单栈顶模式...

    电力系统中基于MATLAB的价格型需求响应与电价弹性矩阵优化

    内容概要:本文详细介绍了如何利用MATLAB进行价格型需求响应的研究,特别是电价弹性矩阵的构建与优化。文章首先解释了电价弹性矩阵的概念及其重要性,接着展示了如何通过MATLAB代码实现弹性矩阵的初始化、负荷变化量的计算以及优化方法。文中还讨论了如何通过非线性约束和目标函数最小化峰谷差,确保用户用电舒适度的同时实现负荷的有效调节。此外,文章提供了具体的代码实例,包括原始负荷曲线与优化后负荷曲线的对比图,以及基于历史数据的参数优化方法。 适合人群:从事电力系统优化、能源管理及相关领域的研究人员和技术人员。 使用场景及目标:适用于希望深入了解并掌握价格型需求响应机制的专业人士,旨在帮助他们更好地理解和应用电价弹性矩阵,优化电力系统的负荷分布,提高能源利用效率。 其他说明:文章强调了实际应用中的注意事项,如弹性矩阵的动态校准和用户价格敏感度的滞后效应,提供了实用的技术细节和实践经验。

    一级医院医疗信息管理系统安装调试技术服务合同20240801.pdf

    一级医院医疗信息管理系统安装调试技术服务合同20240801.pdf

    表5 文献综述.doc

    表5 文献综述.doc

    36W低压输入正激电源, 正激变压器设计方法步骤及谐振电感的设计方法,主要讲诉了正激电源变压器测的输入输出参数,按输入的条件设计相关的变压器的参数,同时将输出电感的设计方法一并例出,详细的设计步骤

    36W低压输入正激电源 变压器电感设计

    基于YOLOv8的深度学习课堂行为检测系统源码(含检测图片和视频)

    基于YOLOv8的深度学习课堂行为检测系统源码,软件开发环境python3.9,系统界面开发pyqt5。在使用前安装python3.9,并安装软件所需的依赖库,直接运行MainProgram.py文件即可打开程序。模型训练时,将train,val数据集的绝对路径改为自己项目数据集的绝对路径,运行train.py文件即可开始进行模型训练,内含项目文件说明,以及检测图片和视频。

    odbc-oracle zabbix模版原版

    odbc_oracle zabbix模版原版

    基于纳什谈判理论的风光氢多主体能源系统合作运行方法——综合能源交易与优化模型

    内容概要:本文探讨了利用纳什谈判理论来优化风光氢多主体能源系统的合作运行方法。通过MATLAB代码实现了一个复杂的优化模型,解决了风电、光伏和氢能之间的合作问题。文中详细介绍了ADMM(交替方向乘子法)框架的应用,包括联盟效益最大化和收益分配谈判两个子任务。此外,还涉及了加权残差计算、目标函数构造、可视化工具以及多种博弈模式的对比等功能模块。实验结果显示,合作模式下系统总成本显著降低,氢能利用率大幅提升。 适合人群:从事能源系统研究的专业人士、对博弈论及其应用感兴趣的学者和技术人员。 使用场景及目标:适用于需要优化多主体能源系统合作运行的场合,如工业园区、电网公司等。主要目标是提高能源利用效率,降低成本,增强系统的灵活性和稳定性。 其他说明:代码中包含了丰富的可视化工具,能够帮助研究人员更好地理解和展示谈判过程及结果。同时,提供了多种博弈模式的对比功能,便于进行性能评估和方案选择。

    C#与Halcon联合编程实现高效视觉几何定位与测量框架

    内容概要:本文详细介绍了如何利用C#与Halcon联合编程构建高效的视觉几何定位与测量框架。主要内容涵盖模板创建与匹配、圆测量、数据持久化以及图像采集等方面的技术细节。首先,通过创建形状模板并进行匹配,实现了工件的精确定位。接着,针对圆形物体的测量,提出了动态ROI绘制、亚像素边缘提取和稳健圆拟合的方法。此外,还讨论了模板管理和图像采集的最佳实践,确保系统的稳定性和高效性。最后,强调了Halcon对象的内存管理和错误处理机制,提供了实用的优化建议。 适合人群:具备一定编程基础,尤其是对C#和Halcon有一定了解的研发人员和技术爱好者。 使用场景及目标:适用于工业生产线上的自动化检测设备开发,旨在提高工件定位和尺寸测量的精度与效率。主要目标是帮助开发者掌握C#与Halcon联合编程的具体实现方法,从而构建稳定可靠的视觉检测系统。 其他说明:文中提供了大量实战代码片段和调试技巧,有助于读者快速理解和应用相关技术。同时,作者分享了许多实际项目中的经验和教训,使读者能够避开常见陷阱,提升开发效率。

    QT6 C++视频播放器实现(基于QGraphicsVideo)

    QT视频播放器实现(基于QGraphicsView)

    评估管线钢环焊缝质量及其对氢脆的敏感性.pptx

    评估管线钢环焊缝质量及其对氢脆的敏感性.pptx

    机器学习(预测模型):专注于 2024 年出现的漏洞(CVE)信息数据集

    该是一个在 Kaggle 上发布的数据集,专注于 2024 年出现的漏洞(CVE)信息。以下是关于该数据集的详细介绍:该数据集收集了 2024 年记录在案的各类漏洞信息,涵盖了漏洞的利用方式(Exploits)、通用漏洞评分系统(CVSS)评分以及受影响的操作系统(OS)。通过整合这些信息,研究人员和安全专家可以全面了解每个漏洞的潜在威胁、影响范围以及可能的攻击途径。数据主要来源于权威的漏洞信息平台,如美国国家漏洞数据库(NVD)等。这些数据经过整理和筛选后被纳入数据集,确保了信息的准确性和可靠性。数据集特点:全面性:涵盖了多种操作系统(如 Windows、Linux、Android 等)的漏洞信息,反映了不同平台的安全状况。实用性:CVSS 评分提供了漏洞严重程度的量化指标,帮助用户快速评估漏洞的优先级。同时,漏洞利用信息(Exploits)为安全研究人员提供了攻击者可能的攻击手段,有助于提前制定防御策略。时效性:专注于 2024 年的漏洞数据,反映了当前网络安全领域面临的新挑战和新趋势。该数据集可用于多种研究和实践场景: 安全研究:研究人员可以利用该数据集分析漏洞的分布规律、攻击趋势以及不同操作系统之间的安全差异,为网络安全防护提供理论支持。 机器学习与数据分析:数据集中的结构化信息适合用于机器学习模型的训练,例如预测漏洞的 CVSS 评分、识别潜在的高危漏洞等。 企业安全评估:企业安全团队可以参考该数据集中的漏洞信息,结合自身系统的实际情况,进行安全评估和漏洞修复计划的制定。

    QML Combobox 自动过滤,输入字符串后自动匹配

    博客主页:https://blog.csdn.net/luoyayun361 QML ComboBox控件,输入关键字后自动过滤包含关键字的列表,方便快速查找列表项

    【人工智能领域】人工智能技术发展历程、核心原理及应用指南:涵盖机器学习、深度学习、NLP和计算机视觉的全面介绍

    内容概要:本文全面介绍了人工智能技术的发展历程、核心技术原理、应用方法及其未来趋势。首先阐述了人工智能的定义和核心目标,随后按时间顺序回顾了其从萌芽到爆发的五个发展阶段。接着详细讲解了机器学习、深度学习、自然语言处理和计算机视觉等核心技术原理,并介绍了使用现成AI服务和开发自定义AI模型的应用方法。此外,还展示了智能客服系统、图像分类应用和智能推荐系统的具体实现案例。针对普通用户,提供了使用大模型的指南和提问技巧,强调了隐私保护、信息验证等注意事项。最后展望了多模态AI、可解释AI等未来发展方向,并推荐了相关学习资源。; 适合人群:对人工智能感兴趣的初学者、技术人员以及希望了解AI技术应用的普通大众。; 使用场景及目标:①帮助初学者快速了解AI的基本概念和发展脉络;②为技术人员提供核心技术原理和应用方法的参考;③指导普通用户如何有效地使用大模型进行日常查询和任务处理。; 其他说明:本文不仅涵盖了AI技术的基础知识,还提供了丰富的实际应用案例和实用技巧,旨在帮助读者全面理解人工智能技术,并能在实际工作中加以应用。同时提醒读者关注AI伦理和版权问题,确保安全合法地使用AI工具。

    本学习由 Matrix 工作室制作并开发,包括算法与数据结构的学习路线和各种题解

    本学习由 Matrix 工作室制作并开发,包括算法与数据结构的学习路线和各种题解。

    基于智慧图书馆基础业务流程Axure11高保真原型设计

    本项目致力于构建基于微服务架构的智慧图书馆管理平台,重点突破多校区图书馆异构系统间的数据壁垒。通过建立统一数据治理规范、部署智能分析模块、重构业务流程引擎,系统性实现以下建设目标:构建跨馆业务数据的标准化整合通道,实施容器化部署的弹性资源管理体系,开发具备机器学习能力的业务辅助决策系统,打造可量化评估的管理效能提升模型,最终形成支持PB级数据处理的分布式存储体系与全维度数据资产图谱。

    mysql中慢sql分析

    根据processlist查询出慢sql 1.修改配置文件中的mysql链接 2.目前是15秒执行一次获取执行时间在5秒上的sql,可以在配置中修改 3.执行后查出的慢sql会记录到log文件夹中以日期命名的txt文件中,可自行查验

    全域通航 低空经济服务平台建设实施方案.pptx

    全域通航 低空经济服务平台建设实施方案.pptx

Global site tag (gtag.js) - Google Analytics