.Net中的多线程编程笔记
void ShowDialog()
{
自己定义一个delegate
delegate void NoParamDelegate();
判断是否需要使用BeginInvoke
if(InvokeRequired)
{
BeginInvoke(new NoParamDelegate(reminderDialog.Show));
return;
}
//创建UI的线程可以直接访问
reminderDialog.Show();
}
2. 同步
lock 方式:
当有多个方法访问同一个变量时,可以使用lock来保证在同一时间内只有一个方法能够访问该变量。 即使被lock包围的代码抛出异常, lock也能保证不会因此而其他函数被hang, 因为其内部包含了try。。。Final。(lock内部是用Monitor实现的)
readonly object objectLock = new object();
String shareString = "This is a string";
void MethodA()
{
lock(objectLock)
{
//access and modify the shareString
}
}
void MethodB()
{
lock(objectLock)
{
//access and modify the shareString
}
}
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:如果实例可以被公共访问,将出现 lock (this) 问题。如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。来看看lock(this)的问题:如果有一个类Class1,该类有一个方法用lock(this)来实现互斥:
{
lock (this)
{
System.Windows.Forms.MessageBox.Show("Method2 End");
}
}
Lock(typeof(MyType))锁定住的对象范围更为广泛,由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例,微软现在建议(原文请参考:http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/SDaskgui06032003.mspx?mfr=true)不要使用lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。
锁住一个字符串更为神奇,只要字符串内容相同,就能引起程序挂起。原因是在.NET中,字符串会被暂时存放,如果两个变量的字符串内容相同的话,.NET会把暂存的字符串对象分配给该变量。所以如果有两个地方都在使用lock(“my lock”)的话,它们实际锁住的是同一个对象。到此,微软给出了个lock的建议用法:锁定一个私有的static 成员变量。
.NET在一些集合类中(比如ArrayList,HashTable,Queue,Stack)已经提供了一个供lock使用的对象SyncRoot,用Reflector工具查看了SyncRoot属性的代码,在Array中,该属性只有一句话:return this,这样和lock array的当前实例是一样的。ArrayList中的SyncRoot有所不同
{
if (this._syncRoot == null)
{
Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
}
return this._syncRoot;
{
lock (this._table.SyncRoot)
{
this._table.Add(key, value);
}
}
lock(myCollection.SyncRoot) {
foreach (Object item in myCollection) {
// Insert your code here.
}
}
3. Monitor
该类功效和lock类似:
System.Threading.Monitor.Enter(obj);
try
{
DoSomething();
}
finally
{
System.Threading.Monitor.Exit(obj);
}
public class Program {
static object ball = new object();
public static void Main() {
Thread threadPing = new Thread( ThreadPingProc );
Thread threadPong = new Thread( ThreadPongProc );
threadPing.Start(); threadPong.Start();
}
static void ThreadPongProc() {
System.Console.WriteLine("ThreadPong: Hello!");
lock ( ball )
for (int i = 0; i < 5; i++){
System.Console.WriteLine("ThreadPong: Pong ");
Monitor.Pulse( ball );
Monitor.Wait( ball );
}
System.Console.WriteLine("ThreadPong: Bye!");
}
static void ThreadPingProc() {
System.Console.WriteLine("ThreadPing: Hello!");
lock ( ball )
for(int i=0; i< 5; i++){
System.Console.WriteLine("ThreadPing: Ping ");
Monitor.Pulse( ball );
Monitor.Wait( ball );
}
System.Console.WriteLine("ThreadPing: Bye!");
}
}
ThreadPing: Ping
ThreadPong: Hello!
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Bye!
同步事件和等待句柄
同步事件和等待句柄用于解决更复杂的同步情况,比如一个一个大的计算步骤包含3个步骤result = first term + second term + third term,如果现在想写个多线程程序,同时计算first term,second term 和third term,等所有3个步骤计算好后再把它们汇总起来,我们就需要使用到同步事件和等待句柄,同步事件分有两个,分别为AutoResetEvent和ManualResetEvent,这两个类可以用来代表某个线程的运行状态:终止和非终止,等待句柄用来判断ResetEvent的状态,如果是非终止状态就一直等待,否则放行,让等待句柄下面的代码继续运行。下面的代码示例阐释了如何使用等待句柄来发送复杂数字计算的不同阶段的完成信号。此计算的格式为:result = first term + second term + third term
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());
}
}
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();
// 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();
// Wait for baseNumber to be calculated.
manualEvent.WaitOne();
// 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();
manualEvent.WaitOne();
secondTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
autoEvents[1].Set();
}
void CalculateThirdTerm(object stateInfo)
{
double preCalc = randomGenerator.NextDouble();
manualEvent.WaitOne();
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;
}
}
4.Semaphore
用来实现 生产者和消费者模型 更合适
5.Mutex
系统级别的同步(进程间的)可以使用Mutex来同步。
构造函数说明:
public Mutex( bool initiallyOwned, string name, out bool createdNew )
如果 name 不为 nullNothingnullptrnull 引用(在 Visual Basic 中为 Nothing) 且 initiallyOwned 为 true,则只有当 createdNew 在调用后为 true 时,调用线程才拥有已命名的互斥体。否则,此线程可通过调用 WaitOne 方法来请求互斥体。
此构造函数初始化 Mutex 对象,该对象表示命名的互斥体。您可以创建多个 Mutex 对象来表示同一个已命名的系统互斥体。
如果创建的已命名互斥体已经具备访问控制安全性,而调用方没有 MutexRights..::.FullControl,则会引发一个异常。若要仅使用对线程活动进行同步所需的权限打开现有的已命名互斥体,请参见 OpenExisting 方法。
如果将 name 指定为 nullNothingnullptrnull 引用(在 Visual Basic 中为 Nothing) 或空字符串,则创建一个局部互斥体,这和调用 Mutex(Boolean) 构造函数一样。这种情况下,createdNew 始终为 true。
由于已命名的互斥体是系统范围的,因此可以使用这些互斥体来协调跨进程边界的资源使用。
让应用程序实现仅启动一个进程:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace SingleInstance
{
class Program
{
static void Main(string[] args)
{
Boolean bNeedToCreateNewInstance;
Mutex mu = new Mutex(true, "TO_OT_SINGLE_INSTANCE", out bNeedToCreateNewInstance);
if (bNeedToCreateNewInstance)
{
Console.WriteLine("Create a new instance");
}
else
{
Console.WriteLine("A old instance exits");
}
Console.ReadLine();
}
}
}