c#多線程的應(yīng)用全面解析
1.使用多線程的幾種方式
(1)不需要傳遞參數(shù),也不需要返回參數(shù)
ThreadStart是一個(gè)委托,這個(gè)委托的定義為void ThreadStart(),沒(méi)有參數(shù)與返回值。
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;//得到當(dāng)前時(shí)間
Random ra = new Random();//隨機(jī)數(shù)對(duì)象
Thread.Sleep(ra.Next(10,100));//隨機(jī)休眠一段時(shí)間
Console.WriteLine(time.Minute + ":" + time.Millisecond);
}
}
(2)需要傳遞單個(gè)參數(shù)
ParameterThreadStart委托定義為void ParameterizedThreadStart(object state),有一個(gè)參數(shù)但是沒(méi)有返回值。
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);//傳遞參數(shù)
}
Thread.Sleep(2000);
Console.Read();
}
public static void Calculate(object arg)
{
Random ra = new Random();//隨機(jī)數(shù)對(duì)象
Thread.Sleep(ra.Next(10, 100));//隨機(jī)休眠一段時(shí)間
Console.WriteLine(arg);
}
}
(3)使用專門的線程類(常用)
使用線程類可以有多個(gè)參數(shù)與多個(gè)返回值,十分靈活!
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();
//等待線程結(jié)束
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(10);
}
Console.WriteLine(mt.Result);//打印返回值
Console.Read();
}
}
public class MyThread//線程類
{
public int Parame { set; get; }//參數(shù)
public int Result { set; get; }//返回值
//構(gòu)造函數(shù)
public MyThread(int parame)
{
this.Parame = parame;
}
//線程執(zhí)行方法
public void Calculate()
{
Random ra = new Random();//隨機(jī)數(shù)對(duì)象
Thread.Sleep(ra.Next(10, 100));//隨機(jī)休眠一段時(shí)間
Console.WriteLine(this.Parame);
this.Result = this.Parame * ra.Next(10, 100);
}
}
(4)使用匿名方法(常用)
使用匿名方法啟動(dòng)線程可以有多個(gè)參數(shù)和返回值,而且使用非常方便!
class Program
{
static void Main(string[] args)
{
int Parame = 100;//當(dāng)做參數(shù)
int Result = 0;//當(dāng)做返回值
//匿名方法
ThreadStart threadStart = new ThreadStart(delegate()
{
Random ra = new Random();//隨機(jī)數(shù)對(duì)象
Thread.Sleep(ra.Next(10, 100));//隨機(jī)休眠一段時(shí)間
Console.WriteLine(Parame);//輸出參數(shù)
Result = Parame * ra.Next(10, 100);//計(jì)算返回值
});
Thread thread = new Thread(threadStart);
thread.Start();//多線程啟動(dòng)匿名方法
//等待線程結(jié)束
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(10);
}
Console.WriteLine(Result);//打印返回值
Console.Read();
}
}
(5)使用委托開啟多線程(多線程深入)
1、用委托(Delegate)的BeginInvoke和EndInvoke方法操作線程
BeginInvoke方法可以使用線程異步地執(zhí)行委托所指向的方法。然后通過(guò)EndInvoke方法獲得方法的返回值(EndInvoke方法的返回值就是被調(diào)用方法的返回值),或是確定方法已經(jīng)被成功調(diào)用。
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任務(wù)開始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任務(wù)完成");
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屬性來(lái)判斷異步調(diào)用是否完成
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任務(wù)開始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任務(wù)完成");
return n;
}
static void Main(string[] args)
{
NewTaskDelegate task = newTask;
IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
//等待異步執(zhí)行完成
while (!asyncResult.IsCompleted)
{
Console.Write("*");
Thread.Sleep(100);
}
// 由于異步調(diào)用已經(jīng)完成,因此, EndInvoke會(huì)立刻返回結(jié)果
int result = task.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.Read();
}
}
3、使用WaitOne方法等待異步方法執(zhí)行完成
WaitOne的第一個(gè)參數(shù)表示要等待的毫秒數(shù),在指定時(shí)間之內(nèi),WaitOne方法將一直等待,直到異步調(diào)用完成,并發(fā)出通知,WaitOne方法才返回true。當(dāng)?shù)却付〞r(shí)間之后,異步調(diào)用仍未完成,WaitOne方法返回false,如果指定時(shí)間為0,表示不等待,如果為-1,表示永遠(yuǎn)等待,直到異步調(diào)用完成。
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任務(wù)開始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任務(wù)完成");
return n;
}
static void Main(string[] args)
{
NewTaskDelegate task = newTask;
IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
//等待異步執(zhí)行完成
while (!asyncResult.AsyncWaitHandle.WaitOne(100, false))
{
Console.Write("*");
}
int result = task.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.Read();
}
}
4、使用回調(diào)方式返回結(jié)果
要注意的是“my.BeginInvoke(3,300, MethodCompleted, my)”,BeginInvoke方法的參數(shù)傳遞方式:
前面一部分(3,300)是其委托本身的參數(shù)。
倒數(shù)第二個(gè)參數(shù)(MethodCompleted)是回調(diào)方法委托類型,他是回調(diào)方法的委托,此委托沒(méi)有返回值,有一個(gè)IAsyncResult類型的參數(shù),當(dāng)method方法執(zhí)行完后,系統(tǒng)會(huì)自動(dòng)調(diào)用MethodCompleted方法。
最后一個(gè)參數(shù)(my)需要向MethodCompleted方法中傳遞一些值,一般可以傳遞被調(diào)用方法的委托,這個(gè)值可以使用IAsyncResult.AsyncState屬性獲得。
class Program
{
private delegate int MyMethod(int second, int millisecond);
//線程執(zhí)行方法
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);
}
//回調(diào)方法
private static void MethodCompleted(IAsyncResult asyncResult)
{
if (asyncResult == null || asyncResult.AsyncState == null)
{
Console.WriteLine("回調(diào)失敗?。?!");
return;
}
int result = (asyncResult.AsyncState as MyMethod).EndInvoke(asyncResult);
Console.WriteLine("任務(wù)完成,結(jié)果:" + result);
}
static void Main(string[] args)
{
MyMethod my = method;
IAsyncResult asyncResult = my.BeginInvoke(3,300, MethodCompleted, my);
Console.WriteLine("任務(wù)開始");
Console.Read();
}
}
5、其他組件的BeginXXX和EndXXX方法
在其他的.net組件中也有類似BeginInvoke和EndInvoke的方法,如System.Net.HttpWebRequest類的BeginGetResponse和EndGetResponse方法。其使用方法類似于委托類型的BeginInvoke和EndInvoke方法,例如:
class Program
{
//回調(diào)函數(shù)
private static void requestCompleted(IAsyncResult asyncResult)
{
if (asyncResult == null || asyncResult.AsyncState==null)
{
Console.WriteLine("回調(diào)失敗");
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("返回流長(zhǎng)度:"+str.Length);
}
static void Main(string[] args)
{
HttpWebRequest request =
(HttpWebRequest)WebRequest.Create("http://www.baidu.com");
//異步請(qǐng)求
IAsyncResult asyncResult = request.BeginGetResponse(requestCompleted, request);
Console.WriteLine("任務(wù)開始");
Console.Read();
}
}
2.線程的狀態(tài)控制
(1)前臺(tái)線程與后臺(tái)線程
Thread.IsBackground屬性為true則是后臺(tái)線程,為false則是前臺(tái)線程。后臺(tái)線程與前臺(tái)線程區(qū)別如下:
a.當(dāng)在主線程中創(chuàng)建了一個(gè)線程,那么該線程的IsBackground默認(rèn)是設(shè)置為false的。
b.當(dāng)主線程退出的時(shí)候,IsBackground=false的線程還會(huì)繼續(xù)執(zhí)行下去,直到線程執(zhí)行結(jié)束。只有IsBackground=true的線程才會(huì)隨著主線程的退出而退出。
c.當(dāng)初始化一個(gè)線程,把Thread.IsBackground=true的時(shí)候,指示該線程為后臺(tái)線程。后臺(tái)線程將會(huì)隨著主線程的退出而退出。
d.原理:只要所有前臺(tái)線程都終止后,CLR就會(huì)對(duì)每一個(gè)活在的后臺(tái)線程調(diào)用Abort()來(lái)徹底終止應(yīng)用程序
(2)由線程類(Thread)啟動(dòng)動(dòng)的線程狀態(tài)控制
使用System.Threading.ThreadState與System.Diagnostics.ThreadState枚舉判斷線程狀態(tài),與Thread.ThreadState 屬性配合使用。
System.Threading.ThreadState枚舉狀態(tài):
System.Diagnostics.ThreadState枚舉狀態(tài):
注意:您的代碼在任何情況下都不應(yīng)使用線程狀態(tài)來(lái)同步線程的活動(dòng)。
(3)由委托啟動(dòng)的線程的狀態(tài)控制
委托的EndInvoke方法阻止當(dāng)前線程運(yùn)行,直到委托異步執(zhí)行完成。
IAsyncResult.IsCompleted屬性表示委托異步執(zhí)行是否完成。
委托的WaitOne方法等待異步方法執(zhí)行完成。
3.多線程訪問(wèn)GUI界面的處理
(1)多線程在GUI編程時(shí)出現(xiàn)的問(wèn)題
在GUI編程時(shí),如果你從非創(chuàng)建這個(gè)控件的線程中訪問(wèn)這個(gè)控件或者操作這個(gè)控件的話就會(huì)拋出這個(gè)異常。這是微軟為了保證線程安全以及提高代碼的效率所做的改進(jìn),但是也給大家?guī)?lái)很多不便。
(2)通過(guò)設(shè)置處理
設(shè)置System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;在你的程序初始化的時(shí)候設(shè)置了這個(gè)屬性,而且在你的控件中使用的都是微軟Framework類庫(kù)中的控件的話,系統(tǒng)就不會(huì)再拋出你上面所說(shuō)的這個(gè)錯(cuò)誤了。
(3)通過(guò)委托處理(建議使用)
//按鈕事件
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(Flush);
thread.IsBackground = true;//設(shè)置成后臺(tái)線程
thread.Start();
}
//線程執(zhí)行的方法
private void Flush()
{
//定義委托
Action action = delegate()
{
this.textBox1.AppendText(DateTime.Now.ToString() + "\r\n");
};
while (true)
{
//判斷能否到當(dāng)前線程操作該組件
if (this.textBox1.InvokeRequired)
{
//不在當(dāng)前線程上操作
this.textBox1.Invoke(action);//調(diào)用委托
}
else
{
//在當(dāng)前線程上操作
this.textBox1.AppendText(DateTime.Now.ToString() + "\r\n");
}
Thread.Sleep(1000);
}
注意:使用該方式不會(huì)有無(wú)響應(yīng)的情況發(fā)生,強(qiáng)烈建議使用該方式。此方式不會(huì)發(fā)生界面無(wú)響應(yīng)的關(guān)鍵點(diǎn):調(diào)用this.textBox1.Invoke(action)就是在擁有this.textBox1對(duì)象的線程(不一定是當(dāng)前線程,多數(shù)是主線程)上調(diào)用action委托,若action委托指向的方法執(zhí)行時(shí)間過(guò)長(zhǎng)就會(huì)使得界面無(wú)響應(yīng)!而該方式中action委托指向的方法執(zhí)行時(shí)間極為短。
(4)調(diào)用控件的Invoke和BeginInvoke方法的區(qū)別
在多線程編程中,我們經(jīng)常要在工作線程中去更新界面顯示,而在多線程中直接調(diào)用界面控件的方法是錯(cuò)誤的做法,正確的做法是將工作線程中涉及更新界面的代碼封裝為一個(gè)方法,通過(guò)Invoke或者BeginInvoke去調(diào)用,兩者的區(qū)別就是Invoke導(dǎo)致工作線程等待,而BeginInvoke則不會(huì)。
而所謂的“一面響應(yīng)操作,一面添加節(jié)點(diǎn)”永遠(yuǎn)只能是相對(duì)的,使UI線程的負(fù)擔(dān)不至于太大而以,因?yàn)榻缑娴恼_更新始終要通過(guò)UI線程去做,我們要做的事情是在工作線程中包攬大部分的運(yùn)算,而將對(duì)純粹的界面更新放到UI線程中去做,這樣也就達(dá)到了減輕UI線程負(fù)擔(dān)的目的了。
(5)Application.DoEvents()調(diào)用消息處理程序
在耗時(shí)的循環(huán)的UI更新的方法中,插入Application.DoEvents(),會(huì)使界面獲得響應(yīng),Application.DoEvents()會(huì)調(diào)用消息處理程序。
(6)使用BackgroundWorker組件
主要的事件及參數(shù):
1.DoWork—當(dāng)執(zhí)行BackgroundWorker.RunWorkerAsync方法時(shí)會(huì)觸發(fā)該事件,并且傳遞DoWorkEventArgs參數(shù);
2.ProgressChanged—操作處理中獲得的處理狀態(tài)變化,通過(guò)BackgroundWorker.ReportProgress方法觸發(fā)該事件,并且傳遞ProgressChangedEventArgs,其中包含了處理的百分比,這個(gè)參數(shù)在UI界面上設(shè)置progressbar控件。
3.RunWorkerCompleted—異步操作完成或中途終止會(huì)觸發(fā)該事件。如果需要提前終止執(zhí)行后臺(tái)操作,可以調(diào)用BackgroundWorker.CancelAsync方法。在處理DoWork事件的函數(shù)中檢測(cè)BackgroundWorker.CancellationPending屬性是否為true,如果是true,則表示用戶已經(jīng)取消了異步調(diào)用,同時(shí)將DoWorkEventArgs.Cancel屬性設(shè)為true(傳遞給處理DoWork事件的函數(shù)的第二個(gè)參數(shù)),這樣當(dāng)退出異步調(diào)用的時(shí)候,可以讓處理RunWorkerCompleted事件的函數(shù)知道是正常退出還是中途退出。
主要的方法:
1. BackgroundWorker.RunWorkerAsync—“起動(dòng)”異步調(diào)用的方法有兩次重載RunWorkerAsync(),RunWorkerAsync(object argument),第二個(gè)重載提供了一個(gè)參數(shù),可以供異步調(diào)用使用。(如果有多個(gè)參數(shù)要傳遞怎么辦,使用一個(gè)類來(lái)傳遞他們吧)。調(diào)用該方法后會(huì)觸發(fā)DoWork事件,并且為處理DoWork事件的函數(shù)傳遞DoWorkEventArg參數(shù),其中包含了RunWorkerAsync傳遞的參數(shù)。在相應(yīng)DoWork的處理函數(shù)中就可以做具體的復(fù)雜操作。
2. BackgroundWorker.ReportProgress—需要在一個(gè)冗長(zhǎng)的操作中向用戶不斷反饋進(jìn)度,這樣的話就可以調(diào)用的ReportProgress(int percent),在調(diào)用 ReportProgress 方法時(shí),觸發(fā)ProgressChanged事件。提供一個(gè)在 0 到 100 之間的整數(shù),它表示后臺(tái)活動(dòng)已完成的百分比。你也可以提供任何對(duì)象作為第二個(gè)參數(shù),允許你給事件處理程序傳遞狀態(tài)信息。作為傳遞到此過(guò)程的 ProgressChangedEventArgs 參數(shù)屬性,百分比和你自己的對(duì)象(如果提供的話)均要被傳遞到 ProgressChanged 事件處理程序。這些屬性被分別命名為 ProgressPercentage 和 UserState,并且你的事件處理程序可以以任何需要的方式使用它們。(注意:只有在BackgroundWorker.WorkerReportsProgress屬性被設(shè)置為true該方法才可用)。
3. BackgroundWorker.CancelAsync—但需要退出異步調(diào)用的時(shí)候,就調(diào)用的這個(gè)方法。但是樣還不夠,因?yàn)樗鼉H僅是將BackgroudWorker.CancellationPending屬性設(shè)置為true。你需要在具體的異步調(diào)用處理的時(shí)候,不斷檢查BackgroudWorker.CancellationPending是否為true,如果是真的話就退出。(注意:只有在BackgroundWorker.WorkerSupportsCancellation屬性被設(shè)置為true該方法才可用)。
//按鈕事件
private void button1_Click(object sender, EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();//處理事務(wù)
}
//處理事務(wù)事件
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//初始化進(jìn)度條
this.progressBar1.Maximum = 100;
this.progressBar1.Minimum = 0;
//模擬事物處理
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10);
//局部操作完成事件觸發(fā)
this.backgroundWorker1.ReportProgress(i, null);
}
}
//局部操作完成時(shí)執(zhí)行的方法
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;//設(shè)置進(jìn)度條值
}
//事物處理完成時(shí)觸發(fā)
private void backgroundWorker1_RunWorkerCompleted(object sender,RunWorkerCompletedEventArgs e)
{
MessageBox.Show(null, "工作線程完成!", "提示");
}
4.線程池
(1)線程池的作用
許多時(shí)候,我們需要用多線程,但是又不希望線程的數(shù)量過(guò)多,這就是線程池的作用,.Net為我們提供了現(xiàn)成的線程池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);//設(shè)置線程池
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), "線程" + i);
}
Console.WriteLine("運(yùn)行結(jié)束");
Console.Read();
}
}
每一個(gè)進(jìn)程都有一個(gè)線程池,線程池的默認(rèn)大小是25,我們可以通過(guò)SetMaxThreads方法來(lái)設(shè)置其最大值。
注意:因?yàn)閃aitCallback委托的原型是void WaitCallback(object state),那沒(méi)有辦法,我們只能將多個(gè)參數(shù)封裝到一個(gè)Object中。
5.線程同步
(1)代碼塊同步(Monitor與lock )
1、說(shuō)明:
使用Monitor類的使用與lock關(guān)鍵字的使用在實(shí)現(xiàn)原理上相同。
Monitor.Enter 方法:在指定對(duì)象上獲取排他鎖。
Monitor.TryEnter 方法:試圖獲取指定對(duì)象的排他鎖。
Monitor.Exit 方法:釋放指定對(duì)象上的排他鎖。
Monitor.Wait 方法:釋放對(duì)象上的鎖并阻塞當(dāng)前線程,直到它重新獲取該鎖。
Monitor.Pulse 方法:通知等待隊(duì)列中的線程鎖定對(duì)象狀態(tài)的更改。
Monitor.PulseAll 方法:通知所有的等待線程對(duì)象狀態(tài)的更改。
2、示例
class Program
{
private int Count = 0;
//線程執(zhí)行方法
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是一個(gè)抽象類,下面是從它繼承來(lái)的幾個(gè)類:
Mutex:一個(gè)同步基元,也可用于進(jìn)程間同步。
AutoResetEvent:通知正在等待的線程已發(fā)生事件。無(wú)法繼承此類。
ManualResetEvent:通知一個(gè)或多個(gè)正在等待的線程已發(fā)生事件。無(wú)法繼承此類。
WaitHandle的幾個(gè)方法:
WaitAll:等待指定數(shù)組中的所有元素收到信號(hào)。
WaitAny:等待指定數(shù)組中的任一元素收到信號(hào)。
WaitOne:當(dāng)在派生類中重寫時(shí),阻塞當(dāng)前線程,直到當(dāng)前的WaitHandle收到信號(hào)。
(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("主線程執(zhí)行完畢");
Console.ReadLine();
}
//線程執(zhí)行方法
private static void Method(Object o)
{
mutex.WaitOne();//等待信號(hào)
for (int i = 0; i < 3; i++)
{
Thread.Sleep(500);
Console.WriteLine(o.ToString() + "循環(huán)" + i);
}
mutex.ReleaseMutex();//釋放信號(hào)
}
}
注意:對(duì)于WaitAll、WaitAny、WaitOne方法的使用請(qǐng)參考AutoResetEvent與ManualResetEvent。
2、使用Mutex控制進(jìn)程間的同步
class Program
{
static void Main(string[] args)
{
bool flag = false;
Mutex mutex = new Mutex(true, "Test", out flag);
//第一個(gè)參數(shù):true--給調(diào)用線程賦予互斥體的初始所屬權(quán)
//第一個(gè)參數(shù):互斥體的名稱
//第三個(gè)參數(shù):返回值,如果調(diào)用線程已被授予互斥體的初始所屬權(quán),則返回true
if (flag)
{
Console.Write("進(jìn)程運(yùn)行...");
}
else
{
Console.Write("這個(gè)進(jìn)程正在運(yùn)行!");
Thread.Sleep(5000);//線程掛起5秒鐘
Environment.Exit(1);//退出程序
}
Console.ReadLine();
}
}
運(yùn)行以上代碼生成的應(yīng)用程序第一個(gè)實(shí)例,會(huì)得到結(jié)果:進(jìn)程運(yùn)行...
保持第一個(gè)運(yùn)行狀態(tài),運(yùn)行第二個(gè)實(shí)例,得到結(jié)果:這個(gè)進(jìn)程正在運(yùn)行!
注意:以上代碼中創(chuàng)建了一個(gè)mutex,從其參數(shù)的解釋中得知,第一個(gè)調(diào)用線程將得到互斥體的初始所屬權(quán),如果不釋放的話,其他的線程得不到互斥體所有權(quán)
(4)使用AutoResetEvent
1、使用WaitAll靜態(tài)方法
理解AutoResetEvent.WaitAll(Waits)靜態(tài)方法:WaitAll靜態(tài)方法就是阻塞當(dāng)前線程,直到Waits數(shù)組里的所有元素都調(diào)用Set()方法發(fā)送信號(hào),再繼續(xù)執(zhí)行當(dāng)前線程。
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()
{
//線程執(zhí)行方法
Console.WriteLine("線程:" + temp);
Thread.Sleep(1000);
Waits[temp].Set();//發(fā)送線程執(zhí)行完畢信號(hào)
};
ThreadStart ts = new ThreadStart(thread);
Thread t = new Thread(ts);
t.Start();
}
AutoResetEvent.WaitAll(Waits);//等待Waits中的所有對(duì)象發(fā)出信號(hào)
Console.WriteLine("線程全部執(zhí)行完畢!");
Console.Read();
}
}
2、使用WaitAny靜態(tài)方法
理解AutoResetEvent.WaitAny(Waits)靜態(tài)方法:WaitAny靜態(tài)方法就是阻塞當(dāng)前線程,只要Waits數(shù)組有一個(gè)元素調(diào)用Set()方法發(fā)送信號(hào),就繼續(xù)執(zhí)行當(dāng)前線程。
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);//等待上一個(gè)線程執(zhí)行完畢
}
//線程執(zhí)行方法
Thread.Sleep(1000);
Waits[temp].Set();//發(fā)送線程執(zhí)行完畢信號(hào)
Console.WriteLine("線程:" + temp+"執(zhí)行完畢");
};
ThreadStart ts = new ThreadStart(thread);
Thread t = new Thread(ts);
t.Start();
}
Console.Read();
}
}
3、使用WaitOne成員方法
理解Wait.WaitOne()成員方法:WaitOne方法就是阻塞當(dāng)前線程,只要Wait對(duì)象調(diào)用了Set()方法發(fā)送信號(hào),就繼續(xù)執(zhí)行當(dāng)前線程。
class Program
{
public static void Main()
{
AutoResetEvent Wait = new AutoResetEvent(false);
for (int i = 0; i < 10; i++)
{
Action thread = delegate()
{
//線程執(zhí)行方法
Thread.Sleep(1000);
Wait.Set();//發(fā)送線程執(zhí)行完畢信號(hào)
Console.WriteLine("線程:" + i+"執(zhí)行完畢");
};
ThreadStart ts = new ThreadStart(thread);
Thread t = new Thread(ts);
t.Start();
Wait.WaitOne();//等待調(diào)用 Waits.Set()
}
Console.Read();
}
}
(5)使用ManualResetEvent與AutoResetEvent的區(qū)別
1、ManualResetEvent與AutoResetEvent的相同點(diǎn)
對(duì)于WaitAll、WaitAny、WaitOne方法的使用ManualResetEvent與AutoResetEvent對(duì)象是沒(méi)有區(qū)別的。
2、ManualResetEvent與AutoResetEvent的區(qū)別
但是對(duì)于Set()方法AutoResetEvent只會(huì)給一個(gè)線程發(fā)送信號(hào),而ManualResetEvent會(huì)給多個(gè)線程發(fā)送信號(hào)。在我們需要同步多個(gè)線程的時(shí)候,就只能采用ManualResetEvent了。至于深層次的原因是,AutoResetEvent在Set()之后,會(huì)將線程狀態(tài)自動(dòng)置為false,而ManualResetEvent在Set()后,線程的狀態(tài)就變?yōu)閠rue了,必須手動(dòng)ReSet()之后,才會(huì)重新將線程置為false。這也就是為什么他們的名字一個(gè)為Auto(自動(dòng)),一個(gè)為Manual(手動(dòng))的原因。
class Program
{
private static ManualResetEvent Wait = new ManualResetEvent(false);
public static void Main()
{
Wait.Set();//設(shè)置線程狀態(tài)為允許執(zhí)行
Thread thread1 = new Thread(Method);
thread1.Start("線程1");
Thread.Sleep(1000);//等待線程1執(zhí)行
Wait.Reset();//必須手動(dòng)復(fù)位線程狀態(tài),使?fàn)顟B(tài)為不允許執(zhí)行
Thread thread2 = new Thread(Method);
thread2.Start("線程2");//線程2將會(huì)一直等待信號(hào)
Console.WriteLine("主線程結(jié)束");
Console.Read();
}
//線程執(zhí)行方法
private static void Method(Object o)
{
Wait.WaitOne();//等待信號(hào)
Console.WriteLine(o.ToString());
}
}
(6)使用Interlocked進(jìn)行原子操作
Interlocked類為多個(gè)線程共享的變量提供原子操作。
原子操作:Interlocked.Increment()操作是一個(gè)原子操作,作用是:Count++ 。原子操作,就是不能被更高等級(jí)中斷搶奪優(yōu)先的操作。由于操作系統(tǒng)大部分時(shí)間處于開中斷狀態(tài),所以,一個(gè)程序在執(zhí)行的時(shí)候可能被優(yōu)先級(jí)更高的線程中斷。而有些操作是不能被中斷的,不然會(huì)出現(xiàn)無(wú)法還原的后果,這時(shí)候,這些操作就需要原子操作。就是不能被中斷的操作。
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);//休眠足夠的時(shí)間等待所有線程執(zhí)行完畢
Console.WriteLine("操作后的結(jié)果:" + Program.Count);
Console.ReadLine();
}
//線程執(zhí)行方法
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進(jìn)行同步控制的問(wèn)題:由于獨(dú)占訪問(wèn)模型不允許任何形式的并發(fā)訪問(wèn),這樣的效率總是不太高。許多時(shí)候,應(yīng)用程序在訪問(wèn)資源時(shí)是進(jìn)行讀操作,寫操作相對(duì)較少。為解決這一問(wèn)題,C#提供了System.Threading.ReaderWriterLock類以適應(yīng)多用戶讀/單用戶寫的場(chǎng)景。該類可實(shí)現(xiàn)以下功能:如果資源未被寫操作鎖定,那么任何線程都可對(duì)該資源進(jìn)行讀操作鎖定,并且對(duì)讀操作鎖數(shù)量沒(méi)有限制,即多個(gè)線程可同時(shí)對(duì)該資源進(jìn)行讀操作鎖定,以讀取數(shù)據(jù)。如果資源未被添加任何讀或?qū)懖僮麈i,那么一個(gè)且僅有一個(gè)線程可對(duì)該資源添加寫操作鎖定,以寫入數(shù)據(jù)。簡(jiǎn)單的講就是:讀操作鎖是共享鎖,允許多個(gè)線程同時(shí)讀取數(shù)據(jù);寫操作鎖是獨(dú)占鎖,同一時(shí)刻,僅允許一個(gè)線程進(jìn)行寫操作。
ReaderWriterLock類:定義支持單個(gè)寫線程和多個(gè)讀線程的鎖。
ReaderWriterLockSlim類:表示用于管理資源訪問(wèn)的鎖定狀態(tài),可實(shí)現(xiàn)多線程讀取或進(jìn)行獨(dú)占式寫入訪問(wèn)。
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)//讀數(shù)據(jù)
{
rwl.AcquireReaderLock(1000 * 20); //申請(qǐng)讀操作鎖,在20s內(nèi)未獲取讀操作鎖,則放棄
Console.WriteLine(o.ToString() + "讀取數(shù)據(jù):" + Program.Count);
Thread.Sleep(500);
rwl.ReleaseReaderLock();//釋放讀操作鎖
}
private static void Write(Object o)//寫數(shù)據(jù)
{
rwl.AcquireWriterLock(1000 * 20);//申請(qǐng)寫操作鎖,在20s內(nèi)未獲取寫操作鎖,則放棄
Thread.Sleep(500);
Console.WriteLine(o.ToString() + "寫數(shù)據(jù):" + (++Program.Count));
rwl.ReleaseWriterLock();//釋放寫操作鎖
}
}
(8)使用Semaphore
Semaphore類:限制可同時(shí)訪問(wèn)某一資源或資源池的線程數(shù)。
class Program
{
private static Semaphore semaphore = new Semaphore(0, 5);//初始化信號(hào)量
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(Method);
thread.Start("線程" + i);
}
semaphore.Release(2);//釋放信號(hào)量2個(gè)
Console.WriteLine("主線程運(yùn)行完畢!");
Console.Read();
}
//線程執(zhí)行方法
private static void Method(object o)
{
semaphore.WaitOne();//等待信號(hào)量
Thread.Sleep(1000);
Console.WriteLine(o.ToString());
semaphore.Release();//釋放信號(hào)量
}
}
其它的線程只有等到主線程釋放才會(huì)執(zhí)行,因?yàn)槲医o信號(hào)量計(jì)數(shù)器的初始值是0,所以其它線程在主線程釋放前都會(huì)被阻塞。而后,我在主線程直接用Release(2)函數(shù)將計(jì)數(shù)器置為2,所以2個(gè)線程可以同時(shí)得到執(zhí)行。
注意:可以給信號(hào)量設(shè)置一個(gè)名稱,這個(gè)名稱是操作系統(tǒng)可見(jiàn)的,因此,可以使用這些信號(hào)量來(lái)協(xié)調(diào)跨進(jìn)程邊界的資源使用。
class Program
{
static void Main(string[] args)
{
//初始信號(hào)量5個(gè),最多信號(hào)量10個(gè)
Semaphore seamphore = new Semaphore(5, 10, "Test");
seamphore.WaitOne();//等待信號(hào)
Console.WriteLine("獲取信號(hào)量 1");
seamphore.WaitOne();//等待信號(hào)
Console.WriteLine("獲取信號(hào)量 2");
seamphore.WaitOne();//等待信號(hào)
Console.WriteLine("獲取信號(hào)量 3");
Console.WriteLine("主線程運(yùn)行完畢!");
Console.Read();
}
}
運(yùn)行兩個(gè)這樣的程序,結(jié)果如下,在第二個(gè)運(yùn)行的示例中,會(huì)將阻塞在第三個(gè)信號(hào)量上:
6.定時(shí)器Timer
(1)常用的3個(gè)Timer類
System.Threading.Timer 提供以指定的時(shí)間間隔執(zhí)行方法的機(jī)制。無(wú)法繼承此類。
System.Timers.Timer 在應(yīng)用程序中生成定期事件。
System.Windows.Forms.Timer 實(shí)現(xiàn)按用戶定義的時(shí)間間隔引發(fā)事件的計(jì)時(shí)器。此計(jì)時(shí)器最宜用于Windows窗體應(yīng)用程序中,并且必須在窗口中使用。
(2)System.Timers.Timer的使用示例
class Program
{
static void Main(string[] args)
{
System.Timers.Timer t = new System.Timers.Timer(1000);//產(chǎn)生事件的時(shí)間間隔1s
t.Elapsed += new ElapsedEventHandler(Method); //到達(dá)時(shí)間的時(shí)候執(zhí)行事件
t.AutoReset = true;//設(shè)置是執(zhí)行一次(false)還是一直執(zhí)行(true)
t.Enabled = true;//是否執(zhí)行System.Timers.Timer.Elapsed事件
Console.WriteLine("完成!");
Console.Read();
}
private static void Method(object source,ElapsedEventArgs e)
{
Console.WriteLine("時(shí)間:"+e.SignalTime);
}
}
相關(guān)文章
使用C#實(shí)現(xiàn)網(wǎng)頁(yè)內(nèi)容保存為圖片并生成壓縮包
這篇文章主要為大家詳細(xì)介紹了如何使用C#實(shí)現(xiàn)網(wǎng)頁(yè)內(nèi)容保存為圖片并生成壓縮包,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02C#中FlagsAttribute屬性在enum中的應(yīng)用詳解
這篇文章主要介紹了C#中FlagsAttribute屬性在enum中的應(yīng)用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10C#使用GZipStream實(shí)現(xiàn)文件的壓縮與解壓
這篇文章主要為大家詳細(xì)介紹了C#使用GZipStream實(shí)現(xiàn)文件的壓縮與解壓,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10C#控制Excel Sheet使其自適應(yīng)頁(yè)寬與列寬的方法
這篇文章主要介紹了C#控制Excel Sheet使其自適應(yīng)頁(yè)寬與列寬的方法,涉及C#操作Excel的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06