C#非同步程式設計:多執行緒基礎Thread類
Thrad類提供了:在不同執行緒上執行方法的能力
Thread類位於System.Threading名稱空間下,學會使用一下幾點技能,便可基本掌握最簡單的多執行緒操作
知識點1:建立並啟動執行緒
class Program { static void Main(string[] args) { Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection)); Console.WriteLine("thread1 is ready to running...."); thread1.Start(); Console.ReadLine(); } } class CommonService { public static void GetConnection() { Console.WriteLine("connect success...."); } }
說明:1、new一個Thread物件,便建立了一個新的執行緒;2、呼叫Start方法,將建立的執行緒的狀態設定為running狀態;作業系統會將該執行緒視為執行,然後尋找空閒時間,在該執行緒上執行GetConnection方法。
知識點2(不常用):暫停(掛起)某個執行緒一段時間
class Program { static void Main(string[] args) { Thread.Sleep(new TimeSpan(0,0,5));//將當前執行緒暫停五秒(暫停的是,Main方法所在的主執行緒) Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection)); Console.WriteLine("thread1 is ready to running...."); thread1.Start(); Console.ReadLine(); } } class CommonService { public static void GetConnection() { Thread.Sleep(TimeSpan.FromSeconds(3));//暫停的是GetConnection方法所在的執行緒 Console.WriteLine("connect success...."); } }
說明:使用Thread類的靜態方法--Sleep(),系統會自動判斷執行的程式碼所處在哪一個執行緒"。1、在主執行緒中使用了Sleep,那麼整個應用會停留在該位置"假死"到指定的時間,才繼續向下執行;2、在子執行緒中使用Sleep,那麼子執行緒會暫停操作,到指定時間才繼續執行。開發工作中,不建議使用Sleep()方法。
知識點3(不常用):在一個執行緒中等待另個執行緒執行完成再繼續
當子執行緒建立並啟動後,主執行緒同一時刻也在執行;子執行緒何時執行完畢是不確定的;如果我們希望等待某個執行緒執行完後,才繼續執行另一個執行緒的程式碼,就需要使用Join()方法。
class Program { static void Main(string[] args) { Thread.Sleep(new TimeSpan(0,0,5));//將Main方法所在的主執行緒暫停五秒 Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection)); Console.WriteLine("thread1 is ready to running...."); thread1.Start(); thread1.Join();//join方法有兩個過載,這裡使用無引數形式的Join()方法來等待執行緒執行結束。 Console.WriteLine("main is ready to completed ...."); Console.ReadLine(); } } class CommonService { public static void GetConnection() { Thread.Sleep(TimeSpan.FromSeconds(3));//暫停的是GetConnection方法所在的執行緒 Console.WriteLine("connect success...."); } }
說明:從執行結果可以看出,使用Join()方法後,執行緒之間便存在了一種同步關係--即:你先幹完,我再幹。
知識點4:前臺執行緒與後臺執行緒
class Program
{
static void Main(string[] args)
{
Thread thread = new Thread(new ThreadStart(CommonService.GetConnection));
/**
* 可以通過解除下面註釋來觀察前臺執行緒與後臺執行緒的區別:
* 未解除註釋--Main方法所在的主執行緒率先退出;GetConnection方法所在的子執行緒是前臺執行緒,應用程式會等待子執行緒中的程式碼執行完畢後才結束程序。
* 解除註釋--Main方法所在的主執行緒率先退出;GetConnection方法所在的子執行緒是後臺執行緒,應用程式檢測程序中沒有正在執行的前臺執行緒後,直接結束程序。
*/
//thread.IsBackground = true;
thread.Start();
}
}
class CommonService
{
public static void GetConnection()
{
Console.WriteLine($"當前正在執行方法所在的執行緒是否為後臺執行緒:{Thread.CurrentThread.IsBackground}");
Thread.Sleep(TimeSpan.FromSeconds(3));
Console.WriteLine("connect success....");
}
}
以控制檯程式為例:1、手動建立的執行緒,預設也是一個"前臺執行緒";3、如果指定IsBackground=true,那麼執行緒被指定為後臺執行緒;4、應用程式會等待所有的前臺執行緒執行完畢後再結束工作(如果程式中只剩下後臺執行緒的話,應用程式會直接結束工作)
知識點5:給執行緒傳遞引數
使用Thread物件的Start方法的過載形式:通過給Start()方法傳遞引數,達到給執行緒傳遞引數
錯誤內容:System.InvalidOperationException:“該執行緒是用不接受引數的 ThreadStart 委託建立的。ThreadStart不接受引數,所以需要一個能夠接收引數的委託:ParameterizedThreadStart
方式1:使用ParameterizedThreadStart建立執行緒、並通過給Start()方法傳遞引數,來給執行緒傳遞引數
private static void TestThreadBase()
{
Thread thread = new Thread(new ParameterizedThreadStart(CommonService.ConnectionWithTarget));
thread.Start(12);
}
class CommonService
{
public static void ConnectionWithTarget(object targetName)
{
Console.WriteLine($"執行緒 {targetName} 連線成功....");
Thread.Sleep(TimeSpan.FromSeconds(5));
Console.WriteLine($"執行緒 {targetName} 退出 ...");
}
}
/*
執行緒 12 連線成功....
執行緒 12 退出 ...
*/
使用ParameterizedThreadStart委託的方式給執行緒傳遞引數,那麼執行執行緒的方法的引數列表就需要和ParameterizedThreadStart委託的引數列表匹配--即:需要改方法定義一個Object型別的形參;假如我希望執行執行緒的方法的引數是Student型別的(如下),那麼使用ParameterizedThreadStart委託,還能奏效麼?
準備一個入參為Student型別的方法
class CommonService
{
public static void ShowTargetInfo(Student student)
{
Console.WriteLine($"{student.StudentName}的性別是{student.StudentSex},身高是{student.StudentHeight}");
}
}
class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public double StudentHeight { get; set; }
public char StudentSex { get; set; }
}
嘗試給ParameterizedThreadStart委託新增ShowTargetInfo方法
上面的報錯告訴我們,ShowTargetInfo方法不匹配ParameterizedThreadStart委託,所以我們需要轉換思路:使用Lambda表示式來例項化ParameterizedThreadStart委託;然後再在Lambda表示式中呼叫ShowTargetInfo方法,這樣ShowTargetInfo也算是在該執行緒上運行了。
方式2:使用Lambda表示式例項化委託型別,然後在Lambda表示式中呼叫方法,達到給執行緒傳遞引數的目的
private static void TestThreadBase()
{
Student student = new Student() { StudentId = 11, StudentName = "小哇", StudentSex = '男', StudentHeight = 170.5 };
//方式1
Thread thread1 = new Thread(new ThreadStart(()=>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(student);
}));
thread1.Start();
//方式2
Thread thread2 = new Thread(new ParameterizedThreadStart(item=>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(student);
}));
thread1.Start();
//方式3
Thread thread3 = new Thread(new ParameterizedThreadStart(item =>
{
var stu = item as Student;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(stu);
}));
thread3.Start(student);
//方式1 簡寫形式 Lambda表示式隱式建立了ThreadStart委託(也是我最常用的給執行緒傳遞引數的形式)
Thread thread4 = new Thread(()=>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(student);
});
thread4.Start();
//方式2 簡寫形式 Lambda表示式隱式建立了ParameterizedThreadStart委託
Thread thread5 = new Thread(item =>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(student);
});
thread5.Start();
//方式3 簡寫形式 Lambda表示式隱式建立了ParameterizedThreadStart委託
Thread thread6 = new Thread(item =>
{
var stu = item as Student;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
CommonService.ShowTargetInfo(stu);
});
thread5.Start(student);
}
向執行緒傳遞引數,本質上是向執行緒所執行的方法傳入實參;利用Lambda表示式呼叫目標方法的形式,使目標方法引數列表的靈活度高,不受限制。
知識點6:在多執行緒中處理異常
當我們建立了一個執行緒並啟動該執行緒,那麼在該執行緒中一旦發生了異常,會發生什麼後果呢?
在工作執行緒中(即:非主執行緒)發生了異常,會導致程式崩潰
private static void TestThreadException()
{
new Thread(()=>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running .....");
Thread.Sleep(3000);
throw new Exception($"從執行緒{Thread.CurrentThread.ManagedThreadId}中丟擲異常....");
}).Start();
}
static void Main(string[] args)
{
TestThreadException();
Console.ReadLine();
}
分析:Lambda表示式中的程式碼片段是執行在子執行緒中的,三秒後該執行緒中丟擲異常;由於沒有處理該異常控制檯程式直接崩潰。
在子執行緒的程式碼外部試圖捕獲異常也是徒勞的
static void Main(string[] args)
{
try
{
TestThreadException();
}
catch (Exception ex)
{
Console.WriteLine("捕捉到了子執行緒的異常{0}", ex.Message);
}
Console.ReadLine();
}
分析:本來我們以為在Main()方法中使用try..catch塊兒來試圖捕獲子執行緒中的異常,但實際卻並未能捕捉到該異常,程式照常崩潰
在子執行緒中處理異常,才是多執行緒中處理異常的正確方式
private static void TestThreadException()
{
new Thread(() =>
{
try
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running .....");
Thread.Sleep(3000);
throw new Exception($"從執行緒{Thread.CurrentThread.ManagedThreadId}中丟擲異常....");
}
catch (Exception ex)
{
Console.WriteLine("捕捉到了子執行緒的異常{0}", ex.Message);
}
}).Start();
}
/*
3 is running .....
捕捉到了子執行緒的異常從執行緒3中丟擲異常....
*/
多執行緒程式設計處理異常:不要線上程中丟擲異常,而是使用try..catch塊兒。
以上便是對多執行緒基礎的知識點的總結,記錄下來以便以後查閱。