多线程编程笔记
来源:互联网 发布:nginx 400 编辑:程序博客网 时间:2024/05/18 09:31
多线程编程笔记(一)
操作线程
1、操作实例
using System;
using System.Threading;
namespace AppThreadDemo
{
class App
{
static public void threadMethod()
{
Console.WriteLine("/n执行ThreadMethod的线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
Thread.Sleep(1000); //休眠1秒
}
static public void ThreadCreate()
{
Console.WriteLine("/n执行ThreadCreate的线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
ThreadStart entry = new ThreadStart(App.threadMethod);//用线程体实例化Thread代理类
Thread thread = new Thread(entry);
thread.Start();
thread.Join();//等待线程结束
}
static public void Main()
{
ThreadCreate();
}
}
}
注意:
A、多个线程可以执行同一个线程体:
Thread thread = new Thread(entry);
Thread thread1 = new Thread(entry);
thread.Start();
thread1.Start();
thread.Join();
thread2.Join();
B、一个线程体执行结束后,不能调用其Start再次启动,试图通过对已终止的线程调用 Start 来重新启动已中止的线程将引发 ThreadStateException。
C、一个启动了的线程不能再次启动。试图通过对已运行的线程调用 Start 来重新启动已中止的线程将引发 ThreadStateException。
(对于B和C,《.net核心技术--原理与架构》中描述为SecurityException异常,用编译器是ThreadStateException异常。应该是笔误吧!)
2、结束线程
A、Abort
调用此方法后,在当前的线程上引发 ThreadAbortException,该异常终止此线程的过程。调用此方法通常会终止线程。
如果对尚未启动的线程调用 Abort,则在线程调用 Start 后再调用Abort 将该线程将中止。例如:
ThreadStart entry = new ThreadStart(App.threadMethod);
Thread thread = new Thread(entry);
Console.WriteLine("终止线程");
thread.Abort();
Console.WriteLine("启动线程");
thread.Start();
thread.Join();
Console.WriteLine("结束线程");
输出为:
终止线程
启动线程
结束线程
可以看出,执行这段代码后,Thread刚启动就马上死亡。
如果对已挂起的线程调用 Abort,则线程被重新开始后再执行Abort将该线程将中止。
如果对被阻塞或正在休眠的线程调用 Abort,则该线程被中断然后中止。
下面这段代码没有进行异常处理,所以异常向外传递,直到最后由公共运行库捕获。
using System;
using System.Threading;
namespace AppThreadDemo
{
class App
{
static public void threadMethod()
{
Console.WriteLine("/n执行ThreadMethod的线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
Thread.Sleep(1000);
Console.WriteLine("threadMethod线程结束");
}
static public void ThreadCreate()
{
Console.WriteLine("/n执行ThreadCreate的线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
ThreadStart entry = new ThreadStart(App.threadMethod);
Thread thread = new Thread(entry);
Console.WriteLine("终止线程");
Console.WriteLine("启动线程");
thread.Start();
Thread.Sleep(500);//主线程休眠0.5秒,等待子线程运行
thread.Abort();
thread.Join();
Console.WriteLine("主线程结束");
}
static public void Main()
{
ThreadCreate();
}
}
}
输出为:
执行ThreadCreate的线程的代码为:1
终止线程
启动线程
执行ThreadMethod的线程的代码为:2
主线程结束
在threadMethod中,启动子线程后,主线程休眠0.5等待子线程执行,子线程执行threadMethod的第1句代码后,休眠1秒等待主线程运行。此当主线程休眠并调用Abort方法结束子线程的时候,子线程仍处于休眠状态。着时候子线程被打断,并结束。所以threadMethod的最后没机会执行。
若将threadMethod进行异常处理,
static public void threadMethod()
{
try
{
Console.WriteLine("/n执行ThreadMethod的线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
Thread.Sleep(1000);
}
catch(Exception e)
{
Console.WriteLine("捕获到异常:{0}",e.ToString());
}
finally
{
Console.WriteLine("threadMethod执行了finally块");
}
Console.WriteLine("threadMethod线程结束"); //这句在调用Abort是不会执行到的;Interrupt会执行到的
}
线程体就会捕获到了一个ThreadAbortException异常,而且finally块也执行了。
如果在执行非托管代码时线程忽略 ThreadAbortException,则当线程开始执行托管代码时,系统将再次引发 ThreadAbortException。
B、Interrupt
该方法打断一个处于WaitSleepJoin状态的线城。
在当前的线程调用该方法后产生ThreadInterruptedException异常。与Abort结束线程不同,调用Abort结束线程的方法将使该线程执行完Finally语句就结束,而调用Interrupt方法则允许线程执行完整个线程体后结束。
如果线程体不采取异常处理措施,则线程在被打断的时候立刻结束。
如果线程不处于WaitSleepJoin状态,则线程下次处于WaitSleepJoin时被打断。
多线程编程笔记(二)
线程池
用线程池编写多线程应用一般步骤:
1、编写工作任务:将所有的工作任务泛放在一个符合WaitCallback接口的函数中
public static void MyAsyncOperation(object state)
{
//...
//要完成的工作任务
}
一般通过state传入一个同步对象
2、往线程池中添加工作项
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
此时将第2个参数asyncOpIsDone作为MyAsyncOperation的state参数执行该委托实例。
3、和工作线程通信
比较典型的做法是主线程传入一个同步对象到工作线程,工作线程在完成任务后利用传入的同步对象通知主线程。
线程池中的线程是按需创建达到。公共运行库根据线程池中工作任务的多少创建合适的线程来处理工作任务。
注意:为了减轻系统的负担,应尽量使每个工作任务尽快的结束。对于那些需要大量CPU时间的工作任务,可利用Thread类创建自由线程来执行。
例程:
using System;
using System.Threading;
namespace AsyncMethod
{
class App
{
public static void MyAsyncOperation(object state)
{
Console.WriteLine("工作任务");
//....
Console.WriteLine("睡眠5秒");
Thread.Sleep(5000);
((AutoResetEvent)state).Set();//将指定事件的状态设置为终止。指示工作认为已完成
Console.WriteLine("工作任务结束");
}
public static void Main()
{
Console.WriteLine("主线程进行异步调用");
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
Console.WriteLine("主线程执行其他任务");
//....
Console.WriteLine("主线程等待工作任务结束");
asyncOpIsDone.WaitOne();//阻塞当前线程,直到当前的 WaitHandle 收到信号。
}
}
}
//下面程序,工作线程和主线程均在同一个应用域中,公共运行库创建了2个工作线程来完成工作线程池中的2个任务。
using System;
using System.Threading;
namespace demoThreadPool
{
class App
{
public static void MyWorkItem(object state)
{
Console.WriteLine("/n当前线程的代码:{0}",Thread.CurrentThread.GetHashCode().ToString());
Console.WriteLine("当前应用域的名称为:{0}",AppDomain.CurrentDomain.FriendlyName);
Thread.Sleep(1000);
((AutoResetEvent)state).Set();
}
public static void demoThreadPool()
{
Console.WriteLine("当前线程的代码:{0}",Thread.CurrentThread.GetHashCode().ToString());
Console.WriteLine("当前应用域的名称为:{0}",AppDomain.CurrentDomain.FriendlyName);
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
AutoResetEvent asyncOpIsDone2 = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyWorkItem),asyncOpIsDone);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyWorkItem),asyncOpIsDone2);
WaitHandle[] handles= new WaitHandle[2];
handles[0] = asyncOpIsDone;
handles[1] = asyncOpIsDone2;
WaitHandle.WaitAll(handles);//此语句也可以用下面语句代替
//handles[0].WaitOne();
//handles[1].WaitOne();
}
public static void Main()
{
demoThreadPool();
}
}
}
方法说明:
public AutoResetEvent(
bool initialState
);
//用一个指示是否将初始状态设置为终止的布尔值初始化 AutoResetEvent 类的新实例。
//initialState
//如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止。
//使用 AutoResetEvent 类可以使某个线程等待,直到某个事件通过调用 AutoResetEvent.Set 将其置于已发信号的状态为止。与 ManualResetEvent 不同,AutoResetEvent 在单个等待线程被释放后由系统自动重置为未发信号的状态。如果没有任何线程正在等待,则该事件对象的状态将保持已发信号的状态。AutoResetEvent 与 Win32 CreateEvent 调用相对应,并将 bManualReset 参数指定为 false。
//还要注意WaitOne,WaitAll,WaitAny的用法
多线程编程笔记(二)
线程池
用线程池编写多线程应用一般步骤:
1、编写工作任务:将所有的工作任务泛放在一个符合WaitCallback接口的函数中
public static void MyAsyncOperation(object state)
{
//...
//要完成的工作任务
}
一般通过state传入一个同步对象
2、往线程池中添加工作项
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
此时将第2个参数asyncOpIsDone作为MyAsyncOperation的state参数执行该委托实例。
3、和工作线程通信
比较典型的做法是主线程传入一个同步对象到工作线程,工作线程在完成任务后利用传入的同步对象通知主线程。
线程池中的线程是按需创建达到。公共运行库根据线程池中工作任务的多少创建合适的线程来处理工作任务。
注意:为了减轻系统的负担,应尽量使每个工作任务尽快的结束。对于那些需要大量CPU时间的工作任务,可利用Thread类创建自由线程来执行。
例程:
using System;
using System.Threading;
namespace AsyncMethod
{
class App
{
public static void MyAsyncOperation(object state)
{
Console.WriteLine("工作任务");
//....
Console.WriteLine("睡眠5秒");
Thread.Sleep(5000);
((AutoResetEvent)state).Set();//将指定事件的状态设置为终止。指示工作认为已完成
Console.WriteLine("工作任务结束");
}
public static void Main()
{
Console.WriteLine("主线程进行异步调用");
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
Console.WriteLine("主线程执行其他任务");
//....
Console.WriteLine("主线程等待工作任务结束");
asyncOpIsDone.WaitOne();//阻塞当前线程,直到当前的 WaitHandle 收到信号。
}
}
}
//下面程序,工作线程和主线程均在同一个应用域中,公共运行库创建了2个工作线程来完成工作线程池中的2个任务。
using System;
using System.Threading;
namespace demoThreadPool
{
class App
{
public static void MyWorkItem(object state)
{
Console.WriteLine("/n当前线程的代码:{0}",Thread.CurrentThread.GetHashCode().ToString());
Console.WriteLine("当前应用域的名称为:{0}",AppDomain.CurrentDomain.FriendlyName);
Thread.Sleep(1000);
((AutoResetEvent)state).Set();
}
public static void demoThreadPool()
{
Console.WriteLine("当前线程的代码:{0}",Thread.CurrentThread.GetHashCode().ToString());
Console.WriteLine("当前应用域的名称为:{0}",AppDomain.CurrentDomain.FriendlyName);
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
AutoResetEvent asyncOpIsDone2 = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyWorkItem),asyncOpIsDone);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyWorkItem),asyncOpIsDone2);
WaitHandle[] handles= new WaitHandle[2];
handles[0] = asyncOpIsDone;
handles[1] = asyncOpIsDone2;
WaitHandle.WaitAll(handles);//此语句也可以用下面语句代替
//handles[0].WaitOne();
//handles[1].WaitOne();
}
public static void Main()
{
demoThreadPool();
}
}
}
方法说明:
public AutoResetEvent(
bool initialState
);
//用一个指示是否将初始状态设置为终止的布尔值初始化 AutoResetEvent 类的新实例。
//initialState
//如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止。
//使用 AutoResetEvent 类可以使某个线程等待,直到某个事件通过调用 AutoResetEvent.Set 将其置于已发信号的状态为止。与 ManualResetEvent 不同,AutoResetEvent 在单个等待线程被释放后由系统自动重置为未发信号的状态。如果没有任何线程正在等待,则该事件对象的状态将保持已发信号的状态。AutoResetEvent 与 Win32 CreateEvent 调用相对应,并将 bManualReset 参数指定为 false。
//还要注意WaitOne,WaitAll,WaitAny的用法
多线程编程笔记(三)
互斥
1、Monitor.Enter和Monitor.Exit
Monitor 类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。
注意:使用 Monitor 锁定对象(即引用类型)而不是值类型。
Monitor 具有以下功能:
它根据需要与某个对象相关联。
它是未绑定的,也就是说可以直接从任何上下文调用它。
不能创建 Monitor 类的实例。
将为每个同步对象来维护以下信息:
对当前持有锁的线程的引用。
对就绪队列的引用,它包含准备获取锁的线程。
对等待队列的引用,它包含正在等待锁定对象状态变化通知的线程。
使用 Enter 和 Exit 方法标记临界区的开头和结尾。
如果临界区是一个连续指令集,则由 Enter 方法获取的锁将保证只有一个线程可以使用锁定对象执行所包含的代码。在这种情况下,建议您将这些指令放在 try 块中,并将 Exit 指令放在 finally 块中。此功能通常用于同步对类的静态或实例方法的访问。
如果实例方法需要同步线程访问,则它将使用当前实例作为要锁定的对象调用 Enter 和对应的 Exit 方法。由于只能有一个线程持有当前实例上的锁,因此该方法一次只能由一个线程来执行。
静态方法是使用当前实例的 Type 作为锁定对象以类似的方式来保护的。Enter 和 Exit 方法提供的功能与 C# lock 语句提供的功能相同。
如果临界区跨越整个方法,则可以通过将 System.Runtime.CompilerServices.MethodImplAttribute 放置在方法上并在 MethodImplAttribute 的构造函数中指定 Synchronized 值来实现上述锁定功能。使用该属性后就不需要 Enter 和 Exit 语句了。请注意,该属性将使当前线程持有锁,直到方法返回;如果可以更早释放锁,则使用 Monitor 类或 C# lock 语句而不是该属性。
尽管锁定和释放给定对象的 Enter 和 Exit 语句可以跨越成员或类的边界或同时跨越两者的边界,但并不推荐这样做。
当选择要同步的对象时,应只锁定私有或内部对象。锁定外部对象可能导致死锁,这是因为不相关的代码可能会出于不同的目的而选择锁定相同的对象。
//实例方法同步线程访问
public class Account
{
int val;
public void Deposit(int x)
{
Monitor.Enter(this);
try
{
val += x;
}
finally
{
Monitor.Exit(this);
}
}
public void WithDraw(int x)
{
Monitor.Exiter(this);
try
{
val -= x;
}
finally
{
Monitor.Exit(this);
}
}
}
//静态方法同步线程访问
public class DemoStatic
{
public static int count =0;
}
public class DemoStaticLock
{
public static void Demo()
{
try
{
Monitor.Enter(typeof(DemoStatic));
DemoStatic.count++;
}
catch(Exception e)
{
Console.WriteLine("捕获异常{0}",e.ToString());
}
finally
{
Monitor.Exit(typeof(DemoStatic));
}
}
}
2、Lock/SyncLock语句
Lock/SyncLock关键字将某个语句标志为临界区。
//实例方法同步线程访问
public class Account
{
int val;
public void Deposit(int x)
{
lock(this)
{
val += x;
}
}
public void WithDraw(int x)
{
lock(this)
{
val -= x;
}
}
}
3、ReaderWriterLock
ReaderWriterLock 用于同步对资源的访问。在任一特定时刻,它允许多个线程同时进行读访问,或者允许单个线程进行写访问。在资源不经常发生更改的情况下,ReaderWriterLock 所提供的吞吐量比简单的一次只允许一个线程的锁(如 Monitor)更高。
在多数访问为读访问,而写访问频率较低、持续时间也比较短的情况下,ReaderWriterLock 的性能最好。多个读线程与单个写线程交替进行操作,所以读线程和写线程都不会长时间阻塞。
注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。
一个线程可以持有读线程锁或写线程锁,但是不能同时持有两者。若要获取写线程锁,请使用 UpgradeToWriterLock 和 DowngradeFromWriterLock,而不要通过释放读线程锁的方式获取。
递归锁请求会增加锁上的锁计数。
读线程和写线程将分别排入各自的队列。当线程释放写线程锁时,此刻读线程队列中的所有等待线程都将被授予读线程锁;当已释放所有读线程锁时,写线程队列中处于等待状态的下一个线程(如果存在)将被授予写线程锁,依此类推。换句话说,ReaderWriterLock 在一组读线程和一个写线程之间交替进行操作。
当写线程队列中有一个线程在等待活动读线程锁被释放时,请求新的读线程锁的线程会排入读线程队列。即使它们能和现有的读线程锁持有者同时一起访问,也不会授予它们的请求;这可以防止写线程被读线程无限期阻塞。
大多数在 ReaderWriterLock 上获取锁的方法都采用超时值。使用超时可以避免应用程序中出现死锁。例如,某个线程可能获取了一个资源上的写线程锁,然后请求第二个资源上的读线程锁;同时,另一个线程获取了第二个资源上的写线程锁,并请求第一个资源上的读线程锁。如果不使用超时,这两个线程将出现死锁。
如果超时间隔过期并且没有授予锁请求,则此方法通过引发 ApplicationException 将控制返回给调用线程。线程可以捕捉此异常并确定下一步要进行的操作。
超时用毫秒表示。如果使用 System.TimeSpan 指定超时,则所用的值是 TimeSpan 所表示的毫秒整数的总和。
下面显示用毫秒表示的有效超时值。
值 说明
-1 Infinite.
0 无超时。
> 0 要等待的毫秒数。
除了 -1 以外,不允许使用负的超时值。如果要使用 -1 以外的负整数来指定超时,系统将使用零(无超时)。如果指定的 TimeSpan 表示的是 -1 以外的负毫秒数,将引发 ArgumentOutOfRangeException。
使用ReaderWriterLock实现互斥:首先实例化ReaderWriterLock;然后在读取临界资源前调用AcquireReaderLock方法,读过程结束后,调用ReleaseReaderLock释放读锁定;在修改临界资源之前,调用AcquireWriterLock方法请求写锁定,在写过程结束后,调用ReleaseWriterLock方法释放写锁定。
//例子
public class Account
{
int val ;
ReaderWriterLock rwl = new ReaderWriterLock();
public int Read()
{
rwl.AcquireReaderLock(Timeout.Infinite);
int iRet = 0;
try
{
iRet = val;
}
finally
{
rwl.ReleaseReaderLock();
}
return iRet;
}
public void Deposit(int x)
{
rwl.AcquireWriterLock(Timeout.Infinite);
try
{
val += x;
}
finally
{
rwl.ReleaseWriterLock();
}
}
}
4、互斥体Mutex
当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。
可以使用 WaitHandle.WaitOne 请求互斥体的所属权。拥有互斥体的线程可以在对 Wait 的重复调用中请求相同的互斥体而不会阻塞其执行。但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。如果线程在拥有互斥体期间正常终止,则互斥体状态设置为终止,并且下一个等待线程获得所属权。如果没有线程拥有互斥体,则互斥体状态为终止。
public class Account
{
int val = 100;
Mutex m = new Mutex();
public void Deposite(int x)
{
m.WaitOne() ; //请求获得互斥对象
try
{
val += x;
}
finally
{
m.ReleaseMutex(); //释放互斥对象
}
}
public void Withdraw(int x)
{
m.WaitOne() ;
try
{
val -= x;
}
finally
{
m.ReleaseMutex();
}
}
}
可以使用Mutex对象在线程之间跨进程进行同步。虽然Mutex不具备Monitor类的所有等待和脉冲功能,但它的确提供了创建可在进程之间使用的命名的互斥的功能。如下代码:
using System;
using System.Threading;
namespace DemoSyncAcrossProc
{
public class App
{
static public void DemoMutex()
{
Mutex mutex = new Mutex(false, "Demo");
Console.WriteLine("创建名为Demo的互斥体");
if(mutex.WaitOne())
Console.WriteLine("得到互斥体");
else
Console.WriteLine("没有得到互斥体");
Console.WriteLine("按任意键退出");
Console.ReadLine();
}
static int Main()
{
DemoMutex();
return 0;
}
}
}
编译后,开启一个命令窗口,运行该程序,不按任何键,程序显示如下:
创建名为Demo的互斥体
得到互斥体
按任意键退出
再开启第2个命令窗口执行该程序,程序显示如下:
创建名为Demo的互斥体
此时,第2个实例被阻塞(因为第1个应用实例还没释放名为Demo的互斥体)。
接着切换到第1个命令行窗口,按任意键结束第1个应用实例,再切换到第2个命令行窗口,则会观测到程序继续执行输出“得到互斥体”。
5、InterLocked
此类的方法可以防止可能在下列情况发生的错误:线程正在更新可由其他线程访问的变量时,计划程序切换上下文;或者两个线程在不同的处理器上同时执行。此类的成员不引发异常。
Increment 和 Decrement 方法递增或递减变量并将结果值存储在单个操作中。在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
(1).将实例变量中的值加载到寄存器中。
(2).增加或减少该值。
(3).在实例变量中存储该值。
如果不使用 Increment 和 Decrement,线程会在执行完前两个步骤后被抢先。然后由另一个线程执行所有三个步骤。当第一个线程重新开始执行时,它改写实例变量中的值,造成第二个线程执行增减操作的结果丢失。
Exchange 方法自动交换指定变量的值。CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。比较和交换操作按原子操作执行。
有InterLocked公开的Exchange和CompareExchange方法采用可以存储引用的Object类型的参数。但是,类型安全要求将所有参数严格类型化为Object;不能对其中一个方法的调用中简单地将对象强制转换为Object,换言之,必须创建Object类型变量,将自定义对象赋给该变量,然后传递该变量。例如:
public class DemoInterLocked
{
Object _x;
public Object X
{
set
{
Object ovalue = value;
InterLocked.CompareExchange(ref _x , ovalue , null);
}
get
{
return _x;
}
}
}
//下面用Interlocked实现互斥的例子
using System ;
using System.Threading;
namespace DemoSyncResource
{
class Resource
{
ReaderWriterLock rwl = new ReaderWriterLock();
public void Read(Int32 threadNum)
{
rwl.AcquireReaderLock(Timeout.Infinite);
try
{
Console.WriteLine("开始读资源(Thread={0})", threadNum);
Thread.Sleep(250);
Console.WriteLine("读取资源结束(Thread={0})", threadNum);
}
finally
{
rwl.ReleaseReaderLock();
}
}
public void Write(Int32 threadNum)
{
rwl.AcquireWriterLock(Timeout.Infinite);
try
{
Console.WriteLine("开始写资源 (Thread={0})", threadNum);
Thread.Sleep(750);
Console.WriteLine("写资源结束 (Thread={0})", threadNum);
}
finally
{
rwl.ReleaseWriterLock();
}
}
}
class App
{
//临界资源
static Int32 numAsyncOps = 4;
//同步对象
static AutoResetEvent asyncAreDone = new AutoResetEvent(false);
//临界资源
static Resource res = new Resource();
public static void Main()
{
for (Int32 threadNum = 0 ;threadNum < 4 ; threadNum++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateResource),threadNum);
}
asyncAreDone.WaitOne();
Console.WriteLine("所有操作都结束");
}
static void UpdateResource(object state)
{
Int32 threadNum = (Int32)state;
if ((threadNum % 2)!=0)
res.Read(threadNum);
else
res.Write(threadNum);
//利用Interlocked.Decrement互斥的修改临界资源
//每执行一个线程都将numAsyncOps减1
//如果numAsyncOps变为0,说明4个线程都执行完了
if(Interlocked.Decrement(ref numAsyncOps) == 0)
asyncAreDone.Set();
}
}
}
这5种实现互斥方法的比较:
A.Monitor.Enter/Monitor.Exit和Lock(obj)/SyncLock都是基于引用对象琐技术。锁跟临界资源捆绑在一起。这2种方法的粒度较粗。
B.Mutex是基于自身的锁。通过将一个临界资源跟一个Mutex实例相关,要求所有的请求该临界资源的线程首先获得跟它相关的Mutex锁。这种方式的锁定粒度可以自由控制,可以是一个对象、一段代码、甚至整个过程。
C.Interlocked提供了基于粒度最细的锁,它不依赖于锁定,而基于原子操作的不可分割性,它使增、减、交换、比较等动作成为一个不可分割的原子操作实现互斥。
多线程编程学习笔记(四)
同步
实现同步的3种方法:
1、Thread.Join()
2、WaitHandle
//使用自动事件
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
asyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
asyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
//使用手工代码
ManualResetEvent masyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),masyncOpIsDone);
masyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
//masyncOpIsDone仍处于有信号状态
//必须手工复位
masyncOpIsDone.Reset(); //如果该代码注释掉,则主线程不会等待第2个子线程结束。
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),masyncOpIsDone);
masyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
3、Monitor
//下面实现同步的例子
using System;
using System.Threading;
namespace AppThreadDemo
{
public class Buffer
{
const int size = 1;
char[] buffer = new char[size];//临界资源
//n缓冲区的字符数
//head队列头
//tail队列尾
int n=0,head = 0, tail =0 ;
public void Put(char ch)
{
Console.WriteLine("Put 开始");
lock(this)
{
n++;//放一个字符
while(n> size){//如果n>size说明缓冲区满,故期待读线程取数据
Console.WriteLine("Put Monitor.Wait");
Monitor.Wait(this);
}
buffer[tail]=ch;
tail = (tail+1) % size ;
Console.WriteLine("Put tail={0}/tbuffer={1}/tn={2}",tail,buffer[tail],n);
if (n <=0) //如果缓冲区为空,则通知所有等待线程
{
Console.WriteLine("Put 通知等待的所有线程");
Monitor.PulseAll(this);
}
}
}
public char Get()
{
char ch;
Console.WriteLine("Get 开始");
lock(this)
{
n--;//先取一个字符
while(n<0)//如果缓冲区为空,则等待写线程写入数据
{
Console.WriteLine("Get Monitor.Wait");
Monitor.Wait(this);
}
ch = buffer[head];
head = (head + 1) % size;
Console.WriteLine("Get tail={0}/tbuffer={1}/tn={2}",tail,buffer[tail],n);
if(n>=size)//如果缓冲区满了,则通知所有等待线程
{
Console.WriteLine("Get 通知等待的所有线程");
Monitor.PulseAll(this);
}
return ch;
}
}
}
class App
{
static public void bufferRead()
{
Object o = AppDomain.CurrentDomain.GetData("Buffer");
if(o!=null)
{
Buffer buffer = (Buffer)o;
for(int i=0;i<8;i++)
{
Console.WriteLine("读线程/t{0}读到字符/t{1}",Thread.CurrentThread.GetHashCode(),buffer.Get());
}
}
Console.WriteLine("读取结束");
}
static public void bufferWrite()
{
Object o = AppDomain.CurrentDomain.GetData("Buffer");
char[] msg ={'A','B','C','D','E','F','G','H','I','J','1','2'};
if(o!=null)
{
Buffer buffer = (Buffer)o;
for(int i=0;i<msg.GetLength(0);i++)
{
Console.WriteLine("写线程/t{0}写字符/t{1}",Thread.CurrentThread.GetHashCode(),msg[i]);
buffer.Put(msg[i]);
}
}
Console.WriteLine("写结束");
}
static public void demoBuffer()
{
Buffer buffer = new Buffer();
AppDomain.CurrentDomain.SetData("Buffer",buffer);
Thread threadReader = new Thread(new ThreadStart(App.bufferRead));
Thread threadWriter = new Thread(new ThreadStart(App.bufferWrite));
threadReader.Start();
threadWriter.Start();
threadWriter.Join();
threadReader.Join();
}
static int Main(string[] args)
{
demoBuffer();
return 0;
}
}
}
3种方法的总结:
A、Thread.Join用以等待特定的线程实例thread结束,常用以主线程和子线程之间的同步。
B、AutoResetEvent和ManualResetEvent用事件信号量的方式实现多个线程之间的同步。
C、Monitor要和Lock/SyncLock语句配合才能实现同步。
多线程编程学习笔记(五)
处理周期事件
1、System.WinForms.Timer
Timer的Tick事件代码:
Interlocked.Increment(ref _count);
2、ThreadPool
A.生成WaitOrTimerCallback事例
B.生成一个同步对象
C.添加到线程池
例1:
/*RegisterWaitForSingleObject
下面的示例演示了几种线程处理功能。
使用 RegisterWaitForSingleObject 将需要执行的任务以 ThreadPool 线程的方式排队。
使用 AutoResetEvent 发出信号,通知执行任务。
用 WaitOrTimerCallback 委托处理超时和信号。
用 RegisteredWaitHandle 取消排入队列的任务。
*/
using System;
using System.Threading;
// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo {
public RegisteredWaitHandle Handle = null;
public string OtherInfo = "default";
}
public class Example {
public static void Main(string[] args) {
// The main thread uses AutoResetEvent to signal the
// registered wait handle, which executes the callback
// method.
AutoResetEvent ev = new AutoResetEvent(false);
TaskInfo ti = new TaskInfo();
ti.OtherInfo = "First task";
// The TaskInfo for the task includes the registered wait
// handle returned by RegisterWaitForSingleObject. This
// allows the wait to be terminated when the object has
// been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject(
ev,
new WaitOrTimerCallback(WaitProc),
ti,
100,
false
);
// The main thread waits three seconds, to demonstrate the
// time-outs on the queued thread, and then signals.
Thread.Sleep(3100);
Console.WriteLine("Main thread signals.");
ev.Set();
// The main thread sleeps, which should give the callback
// method time to execute. If you comment out this line, the
// program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000);
// If you start a thread yourself, you can wait for it to end
// by calling Thread.Join. This option is not available with
// thread pool threads.
}
// The callback method executes when the registered wait times out,
// or when the WaitHandle (in this case AutoResetEvent) is signaled.
// WaitProc unregisters the WaitHandle the first time the event is
// signaled.
public static void WaitProc(object state, bool timedOut) {
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo) state;
string cause = "TIMED OUT";
if (!timedOut) {
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
}
}
例2:
using System;
using System.Threading;
class State
{
private int nCalledTimes = 0;
public void Called()
{
Interlocked.Increment(ref nCalledTimes);
}
public int CalledTimes
{
get
{
return nCalledTimes;
}
}
}
class App
{
static public void PeriodicMethod(object state , bool timeOut)
{
// timeOut为false时,说明等待有效,否则超时
Console.WriteLine("/nThread {0}开始处理定时事件",Thread.CurrentThread.GetHashCode());
if(!timeOut)
Console.WriteLine("获得等待信号");
else
Console.WriteLine("超时事件发生");
if(state!=null)
{
((State)state).Called();
Console.WriteLine("调用了{0}次",((State)state).CalledTimes);
}
Thread.Sleep(100);
Console.WriteLine("Thread {0}处理定时事件完毕/n",Thread.CurrentThread.GetHashCode());
}
static public void Main()
{
AutoResetEvent myEvent = new AutoResetEvent(false);
WaitOrTimerCallback waitOrTimerCallback = new WaitOrTimerCallback(App.PeriodicMethod);
int timeout = 1000;
bool executeOnlyOnce = false;
State state = new State();
ThreadPool.RegisterWaitForSingleObject(myEvent , waitOrTimerCallback , state ,timeout,executeOnlyOnce);
//Thread.Sleep(10000);
myEvent.Set();
Console.WriteLine("按任意键退出");
Console.ReadLine();
}
}
3、System.Threading.Timer
A.实例化一个TimerCallback代理callback
B.创建一个System.Threading.Timer实例timer
C.如果有必要,调用 timer.Change重新设置timer的durTime和period
D.用timer.Dispose释放timer
using System;
using System.Threading;
class State
{
private int threadID = -1;
private AutoResetEvent firstTimerFired = null;
public State(int threadID , AutoResetEvent firstTimerFired)
{
this.threadID = threadID;
this.firstTimerFired = firstTimerFired;
}
public void Show()
{
Console.WriteLine("thread.HashCode={0}/tthreadID={1}在工作",Thread.CurrentThread.GetHashCode(),threadID);
}
public AutoResetEvent FirstTimerFired
{
get
{
return firstTimerFired;
}
set
{
firstTimerFired = value;
}
}
}
class App
{
public static void Main()
{
Console.WriteLine("每2秒执行一次时钟事件");
TimerCallback callback = new TimerCallback(App.CheckStatus);
Timer timer1 = new Timer(callback , null ,1000 ,2000);
AutoResetEvent firstTimerFired = new AutoResetEvent(false);
State state = new State(2,firstTimerFired);
Timer timer2 = new Timer(callback ,state , 5000 ,0);//定时器事件只触发一次,period为0
firstTimerFired.WaitOne();
Console.WriteLine("按回车继续...");
Console.ReadLine();
timer2.Change(2000,1000);
Console.WriteLine("按回车继续...");
Console.ReadLine();
timer1.Dispose();
timer2.Dispose();
}
static void CheckStatus(object state)
{
if (state !=null)
{
((State)state).Show();
if(((State)state).FirstTimerFired != null)
((State)state).FirstTimerFired.Set();
}
else
{
Console.WriteLine("tread.HashCode = {0}/tthreadID={1}在工作",Thread.CurrentThread.GetHashCode(),-1);
}
}
}
4、System.Timers.Timer
基于服务器的计时器的关键编程元素
Timer 组件引发一个名为 Timer.Elapsed 的事件。您可以为这个事件创建处理程序来执行处理要发生的一切。
Timer 组件的一些更重要的属性和方法还包含:
Interval 属性用来设置引发事件的时间范围,以毫秒计。例如,值为 1000 的时间间隔将一秒钟引发一次事件。
AutoReset 属性决定在给定时间间隔过去之后计时器是否继续引发事件。如果设置成 true,计时器继续重新计算时间间隔并引发事件。如果为 false,它在时间间隔过去后只引发一次事件,然后停止。
Start 方法将计时器的 Enabled 属性设置为 true,它允许计时器开始引发事件。如果计时器已经是启用状态,则调用 Start 方法将重置该计时器。
Stop 方法将计时器的 Enabled 属性设置成 false,以防止计时器再引发事件。
A.创建System.Timers.Timer对象kicker
B.设置周期
C.设置AutoReset为true
D.设置kicker的Elapsed事件
E.启动kicker
F.如果需要,可以重新设置kicker的Interval属性
G.停止记时器
using System;
using System.Timers;
using System.Threading;
class App
{
private static DateTime stopTime = new DateTime(2005,4,2);
static void ElapsedHandler(object sender , ElapsedEventArgs e)
{
if (DateTime.Compare(e.SignalTime , stopTime) > 0 )
{
Console.WriteLine("Thread {0} 处理定事事件",Thread.CurrentThread.GetHashCode());
Thread.Sleep(100);
}
}
static public void Main()
{
System.Timers.Timer kicker = new System.Timers.Timer();
kicker.Interval =1000;
kicker.AutoReset = true;
kicker.Elapsed += new ElapsedEventHandler(ElapsedHandler);
kicker.Start();
Thread.Sleep(2100);
Console.WriteLine("改变时间间隔");
kicker.Interval = 2000;
Thread.Sleep(2100);
Console.WriteLine("结束定事器");
//kicker.Stop();
stopTime = DateTime.Now;
Thread.Sleep(2100);
Console.WriteLine("重新启动定事器");
kicker.Start();
Thread.Sleep(8100);
Console.WriteLine("按任意键退出");
//Console.ReadLine();
//Thread.Sleep(14100);
kicker.Stop();
stopTime = DateTime.Now;
}
}
System.Winforms.Timer、System.Threading.Timer、System.Timers.Timer,通过设置定时周期、定时事件、可以启动、终止、再启动定时器、重新设置定时器属性等。功能依次增强。
ThreadPool一旦设置好时钟属性并启动后,就不能对定时器进行控制。
多线程编程学习笔记(六)
线程局部存储(TLS)
存放局部存储步骤:
1、申请数据槽
LocalDataStoreSlot slot = Thread.GetNamedDataSlot("para");
如果不存在名为para的数据槽,将分配一个所有线程均可用的para数据槽
2、往数据槽存放数据
MyPara para = new MyPara();
para.I = i;
Thread.SetData(slot,para);
3、如有必要,释放数据槽
Thread.FreeNamedDataSlot("para");
释放数据槽要小心,该操作将使所有线程存放在被释放的数据槽中的数据丢失。
读取局部存储步骤:
1、根据名字子线程局部存储中获取特定的数据槽
LocalDataStoreSlot slot = Thread.GetNamedDataSlot("para");
2、从数据槽获取数据
Object o = Thread.GetData(slot);
if (o != null)
{
//转化为特定类型
MyPara para = (MyPara) o ;
//....
}
把下面代码生成mydll.dll
using System;
using System.Threading;
namespace Demo
{
public class MyPara : MarshalByRefObject
{
public int I;
}
public class MyDemoObj : MarshalByRefObject
{
public void demoTLS()
{
LocalDataStoreSlot slot = Thread.GetNamedDataSlot("para"); //根据名字获取数据槽
if(slot != null)
{
//获取数据
Object o = Thread.GetData(slot);
Console.WriteLine("得到对象/t{0}",o!=null);
if(o!=null)
{
//转化为特定的对象
MyPara para =(MyPara)o;
Console.WriteLine("应用域={0}/t线程号={1}/tpara={2}",AppDomain.CurrentDomain.FriendlyName,Thread.CurrentThread.GetHashCode(),para.I);
}
else
{
Console.WriteLine("应用域={0}/t线程号={1}",AppDomain.CurrentDomain.FriendlyName,Thread.CurrentThread.GetHashCode());
}
}
}
public void setTLS(int i)
{
LocalDataStoreSlot slot = Thread.GetNamedDataSlot("para");
MyPara para = new MyPara();
para.I = i;
Thread.SetData(slot , para);
}
}
}
把下面代码生成demo.exe
using System;
using System.Threading;
using System.Runtime.Remoting; //ObjectHandle所在的命名空间
namespace AppThreadDemo
{
class App
{
static private void ShowInfo()
{
Console.WriteLine("当前应用域的名字为:{0}",AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("当前线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
}
static private void demoThreadTLSAcrossThreadCallBack()
{
Console.WriteLine("/n");
App.ShowInfo();
Console.WriteLine("/n读取在另一个线程中创建的线程局部存储");
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.demoTLS();
obj.setTLS(200);
obj.demoTLS();
Console.WriteLine("/n");
}
//主线程跟子线程在同一个应用域中执行,子线程不能获取主线程的局部存储中para
//数据槽的数据;子线程对该线程的局部存储中para数据槽中数据的修改,没有改变
//主线程局部存储区中para数据槽中的数据
static private void demoThreadTLS()
{
App.ShowInfo();
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.setTLS(100);
Thread thread = new Thread(new ThreadStart(demoThreadTLSAcrossThreadCallBack));
thread.Start();
thread.Join();
obj.demoTLS();
}
//对于执行在不同应用域的线程数据槽的数据互不访问,即各自槽数据的修改不会影响对方的数据槽
//数据,他们也不能让在别的应用域上的线程访问
static private void demoThreadTLSAcrossAppDomain()
{
App.ShowInfo();
AppDomain child = AppDomain.CreateDomain("ChildDomain",null,null);
ObjectHandle oh = (ObjectHandle)child.CreateInstance("mydll","Demo.MyDemoObj");
Demo.MyDemoObj obj = (Demo.MyDemoObj)oh.Unwrap();
Console.WriteLine("/n在另外一个应用域中设置当前线程的局部存储,并获得");
obj.setTLS(100);
obj.demoTLS();
Console.WriteLine("/n在当前应用域中不能获得其他应用域中的当前线程的局部存储");
Demo.MyDemoObj obj2 = new Demo.MyDemoObj();
obj2.demoTLS();
Console.WriteLine("/n在当前应用域中设置当前线程的局部存储,并获得");
obj2.setTLS(200);
obj2.demoTLS();
Console.WriteLine("/n当前应用域中设置当前的线程的局部存储不会改变当前线程在另一个应用域中的局部存储");
obj.demoTLS();
}
//对于线程池中的工作线程,也可以有自己的线程局部存储.但因为工作线程是按需分配的,可能存在一个工作线程
//执行多个逻辑线程(逻辑线程即工作队列中的一项任务),即一个物理线程的局部存储可以对应了几个逻辑线程的局部存储.
//下面代码显示了:逻辑线程没有独立的线程局部存储,它的局部存储位于执行逻辑线程的物理线程的局部存储中,
//这样第2个逻辑线程执行时从物理线程的局部存储中获得para参数.要是逻辑线程位于不同的应用域则获取数据
//槽中的数据会失败,代码见demoLogicalThreadTLSAcrossDoMain方法.
static private void demoLogicalThreadTLSCallBack(Object state)
{
Console.WriteLine("/n");
App.ShowInfo();
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.demoTLS();
obj.setTLS(100);
obj.demoTLS();
((AutoResetEvent)state).Set();
}
static private void demoLogicalTreadTLS()
{
App.ShowInfo();
Console.WriteLine("/n执行第1个工作池逻辑线程");
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBack),asyncOpIsDone);
asyncOpIsDone.WaitOne();
Console.WriteLine("/n执行第2个工作池逻辑线程");
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBack),asyncOpIsDone);
asyncOpIsDone.WaitOne();
Console.WriteLine("/n执行第3个工作池逻辑线程");
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBack),asyncOpIsDone);
asyncOpIsDone.WaitOne();
}
static private void demoLogicalThreadTLSCallBackAcrossDomain(Object state)
{
Console.WriteLine("/n");
AppDomain child = AppDomain.CreateDomain("ChildDomain",null,null);
ObjectHandle oh = (ObjectHandle) child.CreateInstance("mydll","Demo.MyDemoObj");
Demo.MyDemoObj obj = (Demo.MyDemoObj)oh.Unwrap();
obj.demoTLS();
obj.setTLS(500);
obj.demoTLS();
((AutoResetEvent)state).Set();
}
static private void demoLogicalThreadTLSAcrossDoMain()
{
App.ShowInfo();
Console.WriteLine("/n执行第1个工作池逻辑线程");
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBack),asyncOpIsDone);
asyncOpIsDone.WaitOne();
Console.WriteLine("/n执行第2个工作池逻辑线程");
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBackAcrossDomain),asyncOpIsDone);
asyncOpIsDone.WaitOne();
Console.WriteLine("执行结束");
}
static int Main(string[] args)
{
//demoThreadTLS();
//demoThreadTLSAcrossAppDomain();
//demoLogicalTreadTLS();
demoLogicalThreadTLSAcrossDoMain();
return 0;
}
}
}
局部线程的总结:
1.主线程跟子线程在同一个应用域中执行,子线程不能获取主线程的局部存储中para数据槽的数据;子线程对该线程的局部存储中para数据槽中数据的修改,没有改变主线程局部存储区中para数据槽中的数据
2.对于执行在不同应用域的线程数据槽的数据互不访问,即各自槽数据的修改不会影响对方的数据槽数据,他们也不能让在别的应用域上的线程访问
3.对于线程池中的工作线程,也可以有自己的线程局部存储.但因为工作线程是按需分配的,可能存在一个工作线程执行多个逻辑线程(逻辑线程即工作队列中的一项任务),即一个物理线程的局部存储可以对应了几个逻辑线程的局部存储.
要是逻辑线程位于不同的应用域则获取数据槽中的数据会失败.
线程静态成员(ThreadStatic)
特点:
1、不能被2个不同的线程共享
线程静态成员隶属于特定的线程。同一个静态成员针对不同的线程有不同的实例。
2、只在第1个生成其实例的线程中初始化线程静态成员的初始化
隶属于第1个线程的线程静态成员将根据类声明中指定的初始化函数生成特定的实例,而隶属于第1个以后的线程的静态成员将按照默认情况初始化。在不指定初始值的情况下,如果它是值类型,可依赖初始化为其默认值的字段,如果它是引用类型,则可依赖初始化为空引用(Visual Basic 中为 Nothing)的字段。
3、同一线程的线程静态成员在不同的应用域有不同的实例
线程静态成员跟特定的线程的特定应用域相关。
4、同一线程同一应用域中同类型的对象将共享一个线程静态成员
代码:
1、创建threadstaticdll.dll
using System;
namespace Demo
{
public class MyPara : MarshalByRefObject
{
public int I;
public MyPara()
{
I = 0;
}
public MyPara(int i)
{
I=i;
}
}
public class MyDemoObj : MarshalByRefObject
{
[ThreadStatic]
private static int threadStaticValue = 10;
[ThreadStatic]
private static MyPara para = new MyPara(100);
public MyDemoObj()
{
if (para == null)
para = new MyPara();
}
public void doIncrease()
{
threadStaticValue ++;
para.I ++;
}
public void ShowThreadStaticValue()
{
Console.WriteLine("threadStaticValue = {0}/tpara.I={1}",threadStaticValue,para.I);
}
}
}
2.控制台程序
using System;
using System.Threading;
using System.Runtime.Remoting;
namespace ThreadStaticDemo
{
class App
{
static private void ShowInfo()
{
Console.WriteLine("当前应用域的名字为:{0}",AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("当前线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
}
static private void demoThreadStaticCallBack()
{
App.ShowInfo();
//因为子线程第2个创建Demo.MyDemoObj实例的线程,因此不会触发线程静态成员的初始化过程,
//而直接用默认值初始化
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.ShowThreadStaticValue();
obj.doIncrease();
obj.ShowThreadStaticValue();
Console.WriteLine("线程执行结束");
}
static private void demoThreadSatic()
{
Console.WriteLine("/n");
App.ShowInfo();
//主线程第1个创建Demo.MyDemoObj实例的线程,所以触发线程静态成员的初始化过程
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.ShowThreadStaticValue();
//在主线程等待的过程中,子线程执行demoThreadStaticCallBack,首先生成一个Demo.MyDemoObj对象实例
Thread thread = new Thread(new ThreadStart(App.demoThreadStaticCallBack));
thread.Start();
thread.Join();
obj.ShowThreadStaticValue();
}
static private void demoThreadStaticAcrossAppDomain()
{
App.ShowInfo();
AppDomain child = AppDomain.CreateDomain("ChildDomain",null,null);
ObjectHandle oh = (ObjectHandle)child.CreateInstance("threadstaticdll","Demo.MyDemoObj");
Demo.MyDemoObj obj = (Demo.MyDemoObj) oh.Unwrap();
obj.doIncrease();
obj.doIncrease();
//执行完上面的2个doIncrease()后,threadStaticValue=12,para.I=102
Console.WriteLine("/n主线程在ChildDomain应用域的线程静态成员");
obj.ShowThreadStaticValue();
//因为主线程是第1个在当前应用域创建MyDemoObj对象的线程,所以将
//引发线程静态成员初始化的过程
Demo.MyDemoObj obj2=new Demo.MyDemoObj();
obj2.doIncrease();
obj2.doIncrease();
obj2.doIncrease();
Console.WriteLine("obj2:");
obj2.ShowThreadStaticValue();
//因为主线程在当前应用域创建线程静态成员,所以这时仅执行MyDemoObj
//的构造函数
Demo.MyDemoObj obj3 = new Demo.MyDemoObj();
Console.WriteLine("obj3:");
obj3.doIncrease();//这个值在obj2上加
Console.WriteLine("/n主线程在{0}应用域的静态成员",AppDomain.CurrentDomain.FriendlyName);
obj3.ShowThreadStaticValue();
}
static void Main()
{
//demoThreadSatic();
demoThreadStaticAcrossAppDomain();
}
}
}
- 多线程编程笔记
- Linux 多线程编程笔记
- linux多线程编程笔记
- 多线程编程笔记
- 多线程编程学习笔记
- linux 多线程编程笔记
- c++ 多线程编程 笔记
- Java 多线程编程 笔记
- C++多线程编程笔记
- Linux多线程编程笔记
- 多线程编程笔记
- Linux多线程编程--学习笔记--多线程简介
- c#多线程编程笔记1
- c#多线程编程笔记2
- c#多线程编程笔记3
- c#多线程编程笔记4
- 多线程编程笔记(三)
- 多线程编程笔记(一)
- 删除光标
- 不显示上次使用者用户名
- HP大中华区总裁孙振耀退休感言
- 关于“未指定的错误”解答
- Mac快捷键
- 多线程编程笔记
- [翻译][原创] Python 悖论 (The Python Paradox)
- mysql explain的使用说明
- 书架上出现频率最高的书籍
- 经典String str = new String("abc")内存分配问题
- 应用Eclipse开发GIS之路-实例开发篇
- ATLCPImplMT 封装 ATL 事件激发通过 COM 单元
- WTSRegisterSessionNotification() API to receive session switch notification
- 开博了!