-
C#教程之c#多线程的应用全面解析
1.使用多线程的几种方式
(1)不需要传递参数,也不需要返回参数
ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回值。
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
ThreadStart threadStart = new ThreadStart(Calculate);
Thread thread = new Thread(threadStart);
thread.Start();
}
Thread.Sleep(2000);
Console.Read();
}
public static void Calculate()
{
DateTime time = DateTime.Now;//得到当前时间
Random ra = new Random();//随机数对象
Thread.Sleep(ra.Next(10,100));//随机休眠一段时间
Console.WriteLine(time.Minute + ":" + time.Millisecond);
}
}
(2)需要传递单个参数
ParameterThreadStart委托定义为void ParameterizedThreadStart(object state),有一个参数但是没有返回值。
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
ParameterizedThreadStart tStart = new ParameterizedThreadStart(Calculate);
Thread thread = new Thread(tStart);
thread.Start(i*10+10);//传递参数
}
Thread.Sleep(2000);
Console.Read();
}
public static void Calculate(object arg)
{
Random ra = new Random();//随机数对象
Thread.Sleep(ra.Next(10, 100));//随机休眠一段时间
Console.WriteLine(arg);
}
}
(3)使用专门的线程类(常用)
使用线程类可以有多个参数与多个返回值,十分灵活!
class Program
{
static void Main(string[] args)
{
MyThread mt = new MyThread(100);
ThreadStart threadStart = new ThreadStart(mt.Calculate);
Thread thread = new Thread(threadStart);
thread.Start();
//等待线程结束
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(10);
}
Console.WriteLine(mt.Result);//打印返回值
Console.Read();
}
}
public class MyThread//线程类
{
public int Parame { set; get; }//参数
public int Result { set; get; }//返回值
//构造函数
public MyThread(int parame)
{
this.Parame = parame;
}
//线程执行方法
public void Calculate()
{
Random ra = new Random();//随机数对象
Thread.Sleep(ra.Next(10, 100));//随机休眠一段时间
Console.WriteLine(this.Parame);
this.Result = this.Parame * ra.Next(10, 100);
}
}
(4)使用匿名方法(常用)
使用匿名方法启动线程可以有多个参数和返回值,而且使用非常方便!
class Program
{
static void Main(string[] args)
{
int Parame = 100;//当做参数
int Result = 0;//当做返回值
//匿名方法
ThreadStart threadStart = new ThreadStart(delegate()
{
Random ra = new Random();//随机数对象
Thread.Sleep(ra.Next(10, 100));//随机休眠一段时间
Console.WriteLine(Parame);//输出参数
Result = Parame * ra.Next(10, 100);//计算返回值
});
Thread thread = new Thread(threadStart);
thread.Start();//多线程启动匿名方法
//等待线程结束
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(10);
}
Console.WriteLine(Result);//打印返回值
Console.Read();
}
}
(5)使用委托开启多线程(多线程深入)
1、用委托(Delegate)的BeginInvoke和EndInvoke方法操作线程
BeginInvoke方法可以使用线程异步地执行委托所指向的方法。然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值),或是确定方法已经被成功调用。
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任务开始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任务完成");
return n;
}
static void Main(string[] args)
{
NewTaskDelegate task = newTask;
IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
//EndInvoke方法将被阻塞2秒
int result = task.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.Read();
}
}
2、使用IAsyncResult.IsCompleted属性来判断异步调用是否完成
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任务开始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任务完成");
return n;
}
static void Main(string[] args)
{
NewTaskDelegate task = newTask;
IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
//等待异步执行完成
while (!asyncResult.IsCompleted)
{
Console.Write("*");
Thread.Sleep(100);
}
// 由于异步调用已经完成,因此, EndInvoke会立刻返回结果
int result = task.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.Read();
}
}
3、使用WaitOne方法等待异步方法执行完成
WaitOne的第一个参数表示要等待的毫秒数,在指定时间之内,WaitOne方法将一直等待,直到异步调用完成,并发出通知,WaitOne方法才返回true。当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任务开始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任务完成");
return n;
}
static void Main(string[] args)
{
NewTaskDelegate task = newTask;
IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
//等待异步执行完成
while (!asyncResult.AsyncWaitHandle.WaitOne(100, false))
{
Console.Write("*");
}
int result = task.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.Read();
}
}
4、使用回调方式返回结果
要注意的是“my.BeginInvoke(3,300, MethodCompleted, my)”,BeginInvoke方法的参数传递方式:
前面一部分(3,300)是其委托本身的参数。
倒数第二个参数(MethodCompleted)是回调方法委托类型,他是回调方法的委托,此委托没有返回值,有一个IAsyncResult类型的参数,当method方法执行完后,系统会自动调用MethodCompleted方法。
最后一个参数(my)需要向MethodCompleted方法中传递一些值,一般可以传递被调用方法的委托,这个值可以使用IAsyncResult.AsyncState属性获得。
class Program
{
private delegate int MyMethod(int second, int millisecond);
//线程执行方法
private static int method(int second, int millisecond)
{
Console.WriteLine("线程休眠" + (second * 1000 + millisecond) + "毫秒");
Thread.Sleep(second * 1000 + millisecond);
Random random = new Random();
return random.Next(10000);
}
//回调方法
private static void MethodCompleted(IAsyncResult asyncResult)
{
if (asyncResult == null || asyncResult.AsyncState == null)
{
Console.WriteLine("回调失败!!!");
return;
}
int result = (asyncResult.AsyncState as MyMethod).EndInvoke(asyncResult);
Console.WriteLine("任务完成,结果:" + result);
}
static void Main(string[] args)
{
MyMethod my = method;
IAsyncResult asyncResult = my.BeginInvoke(3,300, MethodCompleted, my);
Console.WriteLine("任务开始");
Console.Read();
}
}
5、其他组件的BeginXXX和EndXXX方法
在其他的.net组件中也有类似BeginInvoke和EndInvoke的方法,如System.Net.HttpWebRequest类的BeginGetResponse和EndGetResponse方法。其使用方法类似于委托类型的BeginInvoke和EndInvoke方法,例如:
class Program
{
//回调函数
private static void requestCompleted(IAsyncResult asyncResult)
{
if (asyncResult == null || asyncResult.AsyncState==null)
{
Console.WriteLine("回调失败");
return;
}
HttpWebRequest hwr = asyncResult.AsyncState as HttpWebRequest;
HttpWebResponse response = (HttpWebResponse)hwr.EndGetResponse(asyncResult);
StreamReader sr = new StreamReader(response.GetResponseStream());
string str = sr.ReadToEnd();
Console.WriteLine("返回流长度:"+str.Length);
}
static void Main(string[] args)
{
HttpWebRequest request =
(HttpWebRequest)WebRequest.Create("http://www.baidu.com");
//异步请求
IAsyncResult asyncResult = request.BeginGetResponse(requestCompleted, request);
Console.WriteLine("任务开始");
Console.Read();
}
}
2.线程的状态控制
(1)前台线程与后台线程
Thread.IsBackground属性为true则是后台线程,为false则是前台线程。后台线程与前台线程区别如下:
a.当在主线程中创建了一个线程,那么该线程的IsBackground默认是设置为false的。
b.当主线程退出的时候,IsBackground=false的线程还会继续执行下去,直到线程执行结束。只有IsBackground=true的线程才会随着主线程的退出而退出。
c.当初始化一个线程,把Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的退出而退出。
d.原理:只要所有前台线程都终止后,CLR就会对每一个活在的后台线程调用Abort()来彻底终止应用程序
(2)由线程类(Thread)启动动的线程状态控制
使用System.Threading.ThreadState与System.Diagnostics.ThreadState枚举判断线程状态,与Thread.ThreadState 属性配合使用。
System.Threading.ThreadState枚举状态:
System.Diagnostics.ThreadState枚举状态:
注意:您的代码在任何情况下都不应使用线程状态来同步线程的活动。
(3)由委托启动的线程的状态控制
委托的EndInvoke方法阻止当前线程运行,直到委托异步执行完成。
IAsyncResult.IsCompleted属性表示委托异步执行是否完成。
委托的WaitOne方法等待异步方法执行完成。
3.多线程访问GUI界面的处理
(1)多线程在GUI编程时出现的问题
在GUI编程时,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常。这是微软为了保证线程安全以及提高代码的效率所做的改进,但是也给大家带来很多不便。
(2)通过设置处理
设置System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;在你的程序初始化的时候设置了这个属性,而且在你的控件中使用的都是微软Framework类库中的控件的话,系统就不会再抛出你上面所说的这个错误了。
(3)通过委托处理(建议使用)
//按钮事件
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(Flush);
thread.IsBackground = true;//设置成后台线程
thread.Start();
}
//线程执行的方法
private void Flush()
{
//定义委托
Action action = delegate()
{
this.textBox1.AppendText(DateTime.Now.ToString() + "\r\n");
};
while (true)
{
//判断能否到当前线程操作该组件
if (this.textBox1.InvokeRequired)
{
//不在当前线程上操作
this.textBox1.Invoke(action);//调用委托
}
else
{
//在当前线程上操作
this.textBox1.AppendText(DateTime.Now.ToString() + "\r\n");
}
Thread.Sleep(1000);
}
注意:使用该方式不会有无响应的情况发生,强烈建议使用该方式。此方式不会发生界面无响应的关键点:调用this.textBox1.Invoke(action)就是在拥有this.textBox1对象的线程(不一定是当前线程,多数是主线程)上调用action委托,若action委托指向的方法执行时间过长就会使得界面无响应!而该方式中action委托指向的方法执行时间极为短。
(4)调用控件的Invoke和BeginInvoke方法的区别
在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过Invoke或者BeginInvoke去调用,两者的区别就是Invoke导致工作线程等待,而BeginInvoke则不会。
而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使UI线程的负担不至于太大而以,因为界面的正确更新始终要通过UI线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到UI线程中去做,这样也就达到了减轻UI线程负担的目的了。
(5)Application.DoEvents()调用消息处理程序
在耗时的循环的UI更新的方法中,插入Application.DoEvents(),会使界面获得响应,Application.DoEvents()会调用消息处理程序。
(6)使用BackgroundWorker组件
主要的事件及参数:
1.DoWork—当执行BackgroundWorker.RunWorkerAsync方法时会触发该事件,并且传递DoWorkEventArgs参数;
2.ProgressChanged—操作处理中获得的处理状态变化,通过BackgroundWorker.ReportProgress方法触发该事件,并且传递ProgressChangedEventArgs,其中包含了处理的百分比,这个参数在UI界面上设置progressbar控件。
3.RunWorkerCompleted—异步操作完成或中途终止会触发该事件。如果需要提前终止执行后台操作,可以调用BackgroundWorker.CancelAsync方法。在处理DoWork事件的函数中检测BackgroundWorker.CancellationPending属性是否为true,如果是true,则表示用户已经取消了异步调用,同时将DoWorkEventArgs.Cancel属性设为true(传递给处理DoWork事件的函数的第二个参数),这样当退出异步调用的时候,可以让处理RunWorkerCompleted事件的函数知道是正常退出还是中途退出。
主要的方法:
1. BackgroundWorker.RunWorkerAsync—“起动”异步调用的方法有两次重载RunWorkerAsync(),RunWorkerAsync(object argument),第二个重载提供了一个参数,可以供异步调用使用。(如果有多个参数要传递怎么办,使用一个类来传递他们吧)。调用该方法后会触发DoWork事件,并且为处理DoWork事件的函数传递DoWorkEventArg参数,其中包含了RunWorkerAsync传递的参数。在相应DoWork的处理函数中就可以做具体的复杂操作。
2. BackgroundWorker.ReportProgress—需要在一个冗长的操作中向用户不断反馈进度,这样的话就可以调用的ReportProgress(int percent),在调用 ReportProgress 方法时,触发ProgressChanged事件。提供一个在 0 到 100 之间的整数,它表示后台活动已完成的百分比。你也可以提供任何对象作为第二个参数,允许你给事件处理程序传递状态信息。作为传递到此过程的 ProgressChangedEventArgs 参数属性,百分比和你自己的对象(如果提供的话)均要被传递到 ProgressChanged 事件处理程序。这些属性被分别命名为 ProgressPercentage 和 UserState,并且你的事件处理程序可以以任何需要的方式使用它们。(注意:只有在BackgroundWorker.WorkerReportsProgress属性被设置为true该方法才可用)。
3. BackgroundWorker.CancelAsync—但需要退出异步调用的时候,就调用的这个方法。但是样还不够,因为它仅仅是将BackgroudWorker.CancellationPending属性设置为true。你需要在具体的异步调用处理的时候,不断检查BackgroudWorker.CancellationPending是否为true,如果是真的话就退出。(注意:只有在BackgroundWorker.WorkerSupportsCancellation属性被设置为true该方法才可用)。
//按钮事件
private void button1_Click(object sender, EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();//处理事务
}
//处理事务事件
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//初始化进度条
this.progressBar1.Maximum = 100;
this.progressBar1.Minimum = 0;
//模拟事物处理
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10);
//局部操作完成事件触发
this.backgroundWorker1.ReportProgress(i, null);
}
}
//局部操作完成时执行的方法
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;//设置进度条值
}
//事物处理完成时触发
private void backgroundWorker1_RunWorkerCompleted(object sender,RunWorkerCompletedEventArgs e)
{
MessageBox.Show(null, "工作线程完成!", "提示");
}
4.线程池
(1)线程池的作用
许多时候,我们需要用多线程,但是又不希望线程的数量过多,这就是线程池的作用,.Net为我们提供了现成的线程池ThreadPool。
(2)线程池的使用
class Program
{
//线程方法
public static void ThreadProc(object i)
{
Console.WriteLine(i.ToString());
Thread.Sleep(1000);
}
public static void Main()
{
ThreadPool.SetMaxThreads(3, 3);//设置线程池
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), "线程" + i);
}
Console.WriteLine("运行结束");
Console.Read();
}
}
每一个进程都有一个线程池,线程池的默认大小是25,我们可以通过SetMaxThreads方法来设置其最大值。
注意:因为WaitCallback委托的原型是void WaitCallback(object state),那没有办法,我们只能将多个参数封装到一个Object中。
5.线程同步
(1)代码块同步(Monitor与lock )
1、说明:
使用Monitor类的使用与lock关键字的使用在实现原理上相同。
Monitor.Enter 方法:在指定对象上获取排他锁。
Monitor.TryEnter 方法:试图获取指定对象的排他锁。
Monitor.Exit 方法:释放指定对象上的排他锁。
Monitor.Wait 方法:释放对象上的锁并阻塞当前线程,直到它重新获取该锁。
Monitor.Pulse 方法:通知等待队列中的线程锁定对象状态的更改。
Monitor.PulseAll 方法:通知所有的等待线程对象状态的更改。
2、示例
class Program
{
private int Count = 0;
//线程执行方法
public void ThreadProc()
{
Monitor.Enter(this);
Thread.Sleep(200);
Count++;
Console.WriteLine(Count);
Monitor.Exit(this);
//等同于
//lock (this)
//{
// Thread.Sleep(200);
// Count++;
// Console.WriteLine(Count);
//}
}
public static void Main()
{
Program p = new Program();
for (int i = 0; i < 100; i++)
{
Thread t = new Thread(p.ThreadProc);
t.Start();
}
Console.Read();
}
}
(2)WaitHandle介绍
WaitHandle是一个抽象类,下面是从它继承来的几个类:
Mutex:一个同步基元,也可用于进程间同步。
AutoResetEvent:通知正在等待的线程已发生事件。无法继承此类。
ManualResetEvent:通知一个或多个正在等待的线程已发生事件。无法继承此类。
WaitHandle的几个方法:
WaitAll:等待指定数组中的所有元素收到信号。
WaitAny:等待指定数组中的任一元素收到信号。
WaitOne:当在派生类中重写时,阻塞当前线程,直到当前的WaitHandle收到信号。
(3)使用Mutex
1、使用Mutex控制线程同步
class Program
{
private static Mutex mutex;
static void Main(string[] args)
{
mutex = new Mutex(false);
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(Method);
thread.Start("线程" + i);
}
Console.WriteLine("主线程执行完毕");
Console.ReadLine();
}
//线程执行方法
private static void Method(Object o)
{
mutex.WaitOne();//等待信号
for (int i = 0; i < 3; i++)
{
Thread.Sleep(500);
Console.WriteLine(o.ToString() + "循环" + i);
}
mutex.ReleaseMutex();//释放信号
}
}
注意:对于WaitAll、WaitAny、WaitOne方法的使用请参考AutoResetEvent与ManualResetEvent。
2、使用Mutex控制进程间的同步
class Program
{
static void Main(string[] args)
{
bool flag = false;
Mutex mutex = new Mutex(true, "Test", out flag);
//第一个参数:true--给调用线程赋予互斥体的初始所属权
//第一个参数:互斥体的名称
//第三个参数:返回值,如果调用线程已被授予互斥体的初始所属权,则返回true
if (flag)
{
Console.Write("进程运行...");
}
else
{
Console.Write("这个进程正在运行!");
Thread.Sleep(5000);//线程挂起5秒钟
Environment.Exit(1);//退出程序
}
Console.ReadLine();
}
}
运行以上代码生成的应用程序第一个实例,会得到结果:进程运行...
保持第一个运行状态,运行第二个实例,得到结果:这个进程正在运行!
注意:以上代码中创建了一个mutex,从其参数的解释中得知,第一个调用线程将得到互斥体的初始所属权,如果不释放的话,其他的线程得不到互斥体所有权
(4)使用AutoResetEvent
1、使用WaitAll静态方法
理解AutoResetEvent.WaitAll(Waits)静态方法:WaitAll静态方法就是阻塞当前线程,直到Waits数组里的所有元素都调用Set()方法发送信号,再继续执行当前线程。
class Program
{
public static void Main()
{
AutoResetEvent[] Waits = new AutoResetEvent[10];
for (int i = 0; i < 10; i++)
{
int temp = i;
Waits[temp] = new AutoResetEvent(false);
Action thread = delegate()
{
//线程执行方法
Console.WriteLine("线程:" + temp);
Thread.Sleep(1000);
Waits[temp].Set();//发送线程执行完毕信号
};
ThreadStart ts = new ThreadStart(thread);
Thread t = new Thread(ts);
t.Start();
}
AutoResetEvent.WaitAll(Waits);//等待Waits中的所有对象发出信号
Console.WriteLine("线程全部执行完毕!");
Console.Read();
}
}
2、使用WaitAny静态方法
理解AutoResetEvent.WaitAny(Waits)静态方法:WaitAny静态方法就是阻塞当前线程,只要Waits数组有一个元素调用Set()方法发送信号,就继续执行当前线程。
class Program
{
public static void Main()
{
AutoResetEvent[] Waits = new AutoResetEvent[10];
for (int i = 0; i < 10; i++)
{
Waits[i] = new AutoResetEvent(false);//初始化Waits
}
for (int i = 0; i < 10; i++)
{
int temp = i;
Action thread = delegate()
{
if (temp > 0)
{
AutoResetEvent.WaitAny(Waits);//等待上一个线程执行完毕
}
//线程执行方法
Thread.Sleep(1000);
Waits[temp].Set();//发送线程执行完毕信号
Console.WriteLine("线程:" + temp+"执行完毕");
};
ThreadStart ts = new ThreadStart(thread);
Thread t = new Thread(ts);
t.Start();
}
Console.Read();
}
}
3、使用WaitOne成员方法
理解Wait.WaitOne()成员方法:WaitOne方法就是阻塞当前线程,只要Wait对象调用了Set()方法发送信号,就继续执行当前线程。
class Program
{
public static void Main()
{
AutoResetEvent Wait = new AutoResetEvent(false);
for (int i = 0; i < 10; i++)
{
Action thread = delegate()
{
//线程执行方法
Thread.Sleep(1000);
Wait.Set();//发送线程执行完毕信号
Console.WriteLine("线程:" + i+"执行完毕");
};
ThreadStart ts = new ThreadStart(thread);
Thread t = new Thread(ts);
t.Start();
Wait.WaitOne();//等待调用 Waits.Set()
}
Console.Read();
}
}
(5)使用ManualResetEvent与AutoResetEvent的区别
1、ManualResetEvent与AutoResetEvent的相同点
对于WaitAll、WaitAny、WaitOne方法的使用ManualResetEvent与AutoResetEvent对象是没有区别的。
2、ManualResetEvent与AutoResetEvent的区别
但是对于Set()方法AutoResetEvent只会给一个线程发送信号,而ManualResetEvent会给多个线程发送信号。在我们需要同步多个线程的时候,就只能采用ManualResetEvent了。至于深层次的原因是,AutoResetEvent在Set()之后,会将线程状态自动置为false,而ManualResetEvent在Set()后,线程的状态就变为true了,必须手动ReSet()之后,才会重新将线程置为false。这也就是为什么他们的名字一个为Auto(自动),一个为Manual(手动)的原因。
class Program
{
private static ManualResetEvent Wait = new ManualResetEvent(false);
public static void Main()
{
Wait.Set();//设置线程状态为允许执行
Thread thread1 = new Thread(Method);
thread1.Start("线程1");
Thread.Sleep(1000);//等待线程1执行
Wait.Reset();//必须手动复位线程状态,使状态为不允许执行
Thread thread2 = new Thread(Method);
thread2.Start("线程2");//线程2将会一直等待信号
Console.WriteLine("主线程结束");
Console.Read();
}
//线程执行方法
private static void Method(Object o)
{
Wait.WaitOne();//等待信号
Console.WriteLine(o.ToString());
}
}
(6)使用Interlocked进行原子操作
Interlocked类为多个线程共享的变量提供原子操作。
原子操作:Interlocked.Increment()操作是一个原子操作,作用是:Count++ 。原子操作,就是不能被更高等级中断抢夺优先的操作。由于操作系统大部分时间处于开中断状态,所以,一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。就是不能被中断的操作。
class Program
{
private static int Count = 0;
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
Thread thread = new Thread(Method);
thread.Start("线程" + i);
}
Thread.Sleep(1000 * 3);//休眠足够的时间等待所有线程执行完毕
Console.WriteLine("操作后的结果:" + Program.Count);
Console.ReadLine();
}
//线程执行方法
private static void Method(Object o)
{
Thread.Sleep(500);
//原子操作,类似:Program.Count++
Interlocked.Increment(ref Program.Count);
//Program.Count++;//非原子操作
Console.WriteLine(o.ToString());
}
}
(7)使用ReaderWriterLock
使用Monitor或Mutex进行同步控制的问题:由于独占访问模型不允许任何形式的并发访问,这样的效率总是不太高。许多时候,应用程序在访问资源时是进行读操作,写操作相对较少。为解决这一问题,C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景。该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定,并且对读操作锁数量没有限制,即多个线程可同时对该资源进行读操作锁定,以读取数据。如果资源未被添加任何读或写操作锁,那么一个且仅有一个线程可对该资源添加写操作锁定,以写入数据。简单的讲就是:读操作锁是共享锁,允许多个线程同时读取数据;写操作锁是独占锁,同一时刻,仅允许一个线程进行写操作。
ReaderWriterLock类:定义支持单个写线程和多个读线程的锁。
ReaderWriterLockSlim类:表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。
class Program
{
private static int Count = 0;//资源
static ReaderWriterLock rwl = new ReaderWriterLock();//读、写操作锁
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(Read);//读线程
thread.Start("线程" + i);
}
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(Write);//写线程
thread.Start("--线程" + i);
}
Console.ReadKey();
}
private static void Read(Object o)//读数据
{
rwl.AcquireReaderLock(1000 * 20); //申请读操作锁,在20s内未获取读操作锁,则放弃
Console.WriteLine(o.ToString() + "读取数据:" + Program.Count);
Thread.Sleep(500);
rwl.ReleaseReaderLock();//释放读操作锁
}
private static void Write(Object o)//写数据
{
rwl.AcquireWriterLock(1000 * 20);//申请写操作锁,在20s内未获取写操作锁,则放弃
Thread.Sleep(500);
Console.WriteLine(o.ToString() + "写数据:" + (++Program.Count));
rwl.ReleaseWriterLock();//释放写操作锁
}
}
(8)使用Semaphore
Semaphore类:限制可同时访问某一资源或资源池的线程数。
class Program
{
private static Semaphore semaphore = new Semaphore(0, 5);//初始化信号量
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(Method);
thread.Start("线程" + i);
}
semaphore.Release(2);//释放信号量2个
Console.WriteLine("主线程运行完毕!");
Console.Read();
}
//线程执行方法
private static void Method(object o)
{
semaphore.WaitOne();//等待信号量
Thread.Sleep(1000);
Console.WriteLine(o.ToString());
semaphore.Release();//释放信号量
}
}
其它的线程只有等到主线程释放才会执行,因为我给信号量计数器的初始值是0,所以其它线程在主线程释放前都会被阻塞。而后,我在主线程直接用Release(2)函数将计数器置为2,所以2个线程可以同时得到执行。
注意:可以给信号量设置一个名称,这个名称是操作系统可见的,因此,可以使用这些信号量来协调跨进程边界的资源使用。
class Program
{
static void Main(string[] args)
{
//初始信号量5个,最多信号量10个
Semaphore seamphore = new Semaphore(5, 10, "Test");
seamphore.WaitOne();//等待信号
Console.WriteLine("获取信号量 1");
seamphore.WaitOne();//等待信号
Console.WriteLine("获取信号量 2");
seamphore.WaitOne();//等待信号
Console.WriteLine("获取信号量 3");
Console.WriteLine("主线程运行完毕!");
Console.Read();
}
}
运行两个这样的程序,结果如下,在第二个运行的示例中,会将阻塞在第三个信号量上:
6.定时器Timer
(1)常用的3个Timer类
System.Threading.Timer 提供以指定的时间间隔执行方法的机制。无法继承此类。
System.Timers.Timer 在应用程序中生成定期事件。
System.Windows.Forms.Timer 实现按用户定义的时间间隔引发事件的计时器。此计时器最宜用于Windows窗体应用程序中,并且必须在窗口中使用。
(2)System.Timers.Timer的使用示例
class Program
{
static void Main(string[] args)
{
System.Timers.Timer t = new System.Timers.Timer(1000);//产生事件的时间间隔1s
t.Elapsed += new ElapsedEventHandler(Method); //到达时间的时候执行事件
t.AutoReset = true;//设置是执行一次(false)还是一直执行(true)
t.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件
Console.WriteLine("完成!");
Console.Read();
}
private static void Method(object source,ElapsedEventArgs e)
{
Console.WriteLine("时间:"+e.SignalTime);
}
}