C#高階篇(6)
1.程序和執行緒
一般情況下,一個應用程式下有一個程序,有好幾個執行緒
在一個程序中有多個執行緒,這些執行緒共享程序的記憶體空間。
在程序中通過互斥鎖,防止多個執行緒同一時間讀寫某一塊記憶體區域。
使用訊號量保證多個執行緒不會相互衝突
通過委託開啟一個執行緒,一般一個比較耗時的操作,開啟單獨的執行緒去執行,比如下載
static void Test() { Console.WriteLine("test"); } static void Main(string[] args) { Action a = Test; a.BeginInvoke(null,null); Console.WriteLine("main"); Console.ReadKey(); }
這裡委託開啟的執行緒和Main是非同步執行的
傳遞一個引數
static void Test(int i) { Console.WriteLine(i+" test"); } static void Main(string[] args) { Action<int> a = Test; a.BeginInvoke(100,null,null); Console.WriteLine("main"); Console.ReadKey(); }
IAsyncResult可以取得當前執行緒的狀態
static int Test(int i) { Console.WriteLine(i+" test"); Thread.Sleep(10);//讓當前執行緒休眠 單位ms return 66; } static void Main(string[] args) { Func<int,int> a = Test; IAsyncResult ar = a.BeginInvoke(100,null,null); Console.WriteLine("main"); while(ar.IsCompleted == false) { Console.Write("."); } int res = a.EndInvoke(ar); Console.WriteLine(res); Console.ReadKey(); }
通過BeginInvoke方法開啟一個執行緒,通過EndInvoke結束執行緒,執行緒結束方法才能有返回值
2.檢測委託執行緒的結束-通過控制代碼和回撥函式
通過控制代碼檢測執行緒結束
ar獲得一個AsyncWaitHandle等待的控制代碼,呼叫WaitOne方法,等待1000毫秒,超時返回false。
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);//1000表示超時時間,如果等待了1000執行緒還沒有結束,返回false。
if(isEnd)
{
int res = a.EndInvoke(ar);
Console.WriteLine(res);
}
通過回撥函式檢測執行緒結束
將回調函式作為BeginInvoke引數傳遞,最後一個引數為給回撥函式傳遞資料的
在回撥函式中通過ar.AsyncState獲取傳過來的資料(傳入的委託a)
static void Main(string[] args)
{ //通過回撥函式檢測執行緒結束
Func<int, int> a = Test;
IAsyncResult ar = a.BeginInvoke(100,OnCallBack,a);
Console.ReadKey();
}
static void OnCallBack(IAsyncResult ar)
{
Console.WriteLine("子執行緒END");
Func<int, int> a = ar.AsyncState as Func<int, int>;
int res = a.EndInvoke(ar);
Console.WriteLine(res + "在回撥函式中取得結果");
}
Func<int, int> a = Test;
//IAsyncResult ar = a.BeginInvoke(100,OnCallBack,a);
a.BeginInvoke(100, ar =>
{
int res = a.EndInvoke(ar);
Console.WriteLine(res + "lambda表示式");
},null);
通過Lambda表示式來結束
3.執行緒開啟:通過Thread類
class Program
{
static void DownloadFile()
{
Console.WriteLine("開始下載"+Thread.CurrentThread.ManagedThreadId);//獲取執行緒ID
Thread.Sleep(2000);
Console.WriteLine("END");
}
static void Main(string[] args)
{
//創建出來Thread,這個執行緒並沒有啟動
Thread t = new Thread(DownloadFile);
t.Start();
Console.WriteLine("Main");
Console.ReadKey();
}
}
通過Lambda表示式開啟執行緒
Thread t = new Thread(() =>
{
Console.WriteLine("開始下載 " + Thread.CurrentThread.ManagedThreadId);//獲取執行緒ID
Thread.Sleep(2000);
Console.WriteLine("END");
});
t.Start();
通過建立物件來建立執行緒
先定義一個類
再建立一個這個類的物件,再構造一個thread物件的時候,可以傳遞一個靜態方法也可以傳遞一個普通的方法。
class MyThread
{
private string filename;
private string filepath;
public MyThread(string fileName,string filePath)
{
this.filename = fileName;
this.filepath = filePath;
}
public void DownFile()
{
Console.WriteLine("開始下載" + filepath + filename);
Thread.Sleep(2000);
Console.WriteLine("End");
}
}
MyThread my = new MyThread("xxx.bt", "www.baidu.com");
Thread t = new Thread(my.DownFile);
t.Start();
4.執行緒中的後臺執行緒和前臺執行緒
只要存在沒有結束的前臺執行緒,應用程式的程序就沒有結束
預設情況下Thread類建立的執行緒是前臺執行緒,執行緒池中的執行緒是後臺執行緒
如果所有前臺執行緒已經結束,那麼後臺執行緒會被終止
通過執行緒的IsBackground方法,將執行緒設定為後臺執行緒
Thread t = new Thread(DownloadFile);
t.IsBackground = true;
t.Start("Loading");
執行緒的優先順序
執行緒會根據優先順序來執行
控制執行緒
Running或者Unstarted。再呼叫了Start方法後,都能帶作業系統的執行緒排程器選擇要執行得執行緒,
這個執行緒才會衝Unstarted修改為Running。使用Thread.Sleep()方法可以讓當前執行緒休眠進入WaitSleepJoin狀態
Abort方法會停止執行緒,這個方法會在要終止得執行緒丟擲一個ThreadAbortException得異常,我們可以通過try catch來處理這個以常,
然後再執行緒結束進行一些清理工作。
如果需要等待執行緒得結束,可以呼叫Join方法,表示吧Thread加入進來,停止當前執行緒,並把它設定為WaitSleepJoin得狀態,直到加入的執行緒完成為止。
5.執行緒池
Thread Pool一個執行緒池中有事先建立好的執行緒。使用執行緒池做一些小任務,創建出來的執行緒預設是後臺執行緒。
執行緒池開啟執行緒得方法要傳遞一個引數
class Program
{
static void ThreadMethod(object state)
{
Console.WriteLine("執行緒開始"+Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.WriteLine("執行緒結束");
}
static void Main(string[] args)
{
//開啟一個工作執行緒
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
Console.ReadKey();
}
}
6.任務
給任務傳遞一個方法讓他執行
任務開啟一個執行緒傳遞的方法不能帶有引數
class Program
{
static void ThreadMethod()
{
Console.WriteLine("任務開始");
Thread.Sleep(2000);
Console.WriteLine("任務結束");
}
static void Main(string[] args)
{
Task t = new Task(ThreadMethod);
t.Start();
Console.WriteLine("Main");
Console.ReadKey();
}
}
或者通過TaskFactory工廠模式開啟一個任務,返回一個Task物件。
TaskFactory tf = new TaskFactory();
Task t = tf.StartNew(ThreadMethod);
連續任務
如果任務t1的執行是依賴於另一個任務t2的,那麼就需要再這個任務t2執行完畢後才開始執行t1.這個時候我們可以使用連續任務
任務層次結構
我們在一個任務中啟動另一個新的任務,相當於新的任務是當前任務的子任務,兩個任務非同步執行,如果父任務執行完了但是子任務沒有執行完
他的狀態會設定為WaitingForChildrenToComplete,只有子任務也執行完了,父任務的狀態就變成RunToCompletion
7.執行緒問題-爭用條件和死鎖
eg:
建立一個類,定義一個方法,先將state++,再判斷當state == 5 的時候列印state =5.
class MyThreadObject
{
private int state = 5;
public void ChangeState()
{
state++;
if(state == 5)
{
Console.WriteLine("state =5");
}
state = 5;
}
}
在main函式中開啟兩個執行緒,這兩個執行緒都是迴圈呼叫MyThreadObject中的方法。
class Program
{
static void ChangeState(object o)
{
MyThreadObject m = o as MyThreadObject;
while (true)
{
m.ChangeState();
}
}
static void Main(string[] args)
{
MyThreadObject m = new MyThreadObject();
Thread t = new Thread(ChangeState);
t.Start(m);
Thread d = new Thread(ChangeState);
d.Start(m);
Console.ReadKey();
}
}
結果輸出了很多state = 5
兩個執行緒修改同一個屬性,但是兩個執行緒不同步,這樣這個屬性的值,就不能確定了。
解決這個問題要給當前物件加一個鎖
修改程式碼
使用Lock鎖定m,這樣如果使用m,就會鎖定m,如果其他執行緒也呼叫m,就會等待m解除鎖定才能使用m。
static void ChangeState(object o)
{
MyThreadObject m = o as MyThreadObject;
while (true)
{
lock (m)
{
m.ChangeState();
}
}
}
死鎖
兩個執行緒都呼叫了兩個屬性。但是他們對屬性的鎖的先後順序不一樣,這樣一個執行緒鎖一個屬性。兩個執行緒都申請不到另一個屬性。
形成了死鎖。
在程式設計的時候設計好鎖定的順序。避免出現死鎖的情況。