隨着.net版本不斷升級,目前多種多線程實現方法
一 .Thread 最基本
1.優缺點
優點--Thread API豐富
缺點-- 1.線程資源是操作系統管理的,對API響應並不靈敏,(也就是調用一次提供的API可能不會立即響應)難以控制
2.線程啟動數量是沒有控制的,可能會導致死機等意外發生
2.Thread對象實例化方法(四種)
2.1聲明一個無參的、返回值為void的委托ThreadStart,委托內含一個靜態方法;
2.2 聲明一個無參的、返回值為void的委托ThreadStart,委托內含一個對象方法;
2.3 直接使用匿名委托;
2.4 直接使用Lambda表達式;
namespace ThreadTest { class Program { static void Main(string[] args) { Console.WriteLine($"主線程,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Thread thread1 = new Thread(new ThreadStart(NewThreadDisplay));//第一種 AnotherObject another = new AnotherObject(); Thread thread2 = new Thread(new ThreadStart(another.AnotherThreadDisplay));//第二種 Thread thread3 = new Thread(delegate() { Console.WriteLine($"子線程3,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); } );//第三種 Thread thread4 = new Thread(()=> Console.WriteLine($"子線程4,ThreadId: {Thread.CurrentThread.ManagedThreadId}"));//第四種 thread1.Start();//啟動子線程1 thread2.Start();//啟動子線程2 thread3.Start();//啟動子線程3 thread4.Start();//啟動子線程4 Console.ReadKey(); } static void NewThreadDisplay() { Console.WriteLine($"子線程1,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); } } class AnotherObject { public void AnotherThreadDisplay() { Console.WriteLine($"子線程2,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); } } }
輸出結果:
主線程,ThreadId: 1
子線程3,ThreadId: 5
子線程4,ThreadId: 6
子線程1,ThreadId: 3
子線程2,ThreadId: 4
可以看出多線程啟用時無序的,即使一個線程在是先start的,執行也可能在后start的線程后面;並且每次執行的先后順序都不一定相同
3.關於線程優先級 Priority屬性
Priority屬性是一個ThreadPriority型枚舉,列舉了5個優先等級:Highest、AboveNormal、Normal(普通線程默認)、BelowNormal、Lowest
4.關於線程的狀態
通過ThreadState可以檢測線程是處於Unstarted、Sleeping、Running 等等狀態,它比 IsAlive 屬性能提供更多的特定信息。
通過CurrentContext可以獲取線程當前的上下文。
CurrentThread是最常用的一個屬性,它是用於獲取當前運行的線程。
5.列舉一些API(可能有坑)
終止-Abort() GetDomain()-返回當前線程正在其中運行的當前域 GetDomainId() Interrupt()-中斷處於 WaitSleepJoin 線程狀態的線程
Join() - 已重載。 阻塞調用線程,直到某個線程終止時為止 Resume()-繼續運行已掛起的線程 Start()-執行本線程 Suspend()-掛起當前線程
Sleep()- 把正在運行的線程掛起一段時間
6.關於前台線程和后台線程
前台線程:只有所有的前台線程都結束,應用程序才能結束。默認情況下創建的線程都是前台線程
后台線程:只要所有的前台線程結束,后台線程自動結束。通過Thread.IsBackground設置后台線程。必須在調用Start方法之前設置線程的類型,否則一旦線程運行,將無法改變其類型。
通過BeginXXX方法運行的線程都是后台線程。
注1:后台線程一般用於處理不重要的事情,應用程序結束時,后台線程是否執行完成對整個應用程序沒有影響。如果要執行的事情很重要,需要將線程設置為前台線程。
二. ThreadPool
.NetFramework 2.0 新增
基於池化資源管理設計思想,線程是一種資源,每次要用線程,就去申請一個線程,使用完釋放;池化就是一種容器,容器提前申請5個線程,程序需要使用線程直接找容器獲取,
用完再放回(空置狀態,避免頻繁申請和銷毀,容易還會根據限制的數量去申請和釋放)
優點:1線程復用 2限制最大線程數量
缺點:API太少了,在線程順序控制上弱,用起來不方便
三.Task--.NetFramework 3.0 新增
比較常用的多線程方式,全部是線程池線程、提供豐富API
Task線程全部是線程池線程、提供豐富API
Action action = o=>{xxxx;}
Task task =new Task(action);
task.Start();
舉例:
namespace ThreadTest { class Program { static void Main(string[] args) { Console.WriteLine($"主線程開始,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Task task1 = new Task(() => Console.WriteLine($"子線程1,ThreadId: {Thread.CurrentThread.ManagedThreadId}")); task1.Start(); //也可以直接創建並啟用 List<Task> taskList = new List<Task>(); taskList.Add(Task.Run(() => Console.WriteLine($"子線程2,ThreadId: {Thread.CurrentThread.ManagedThreadId}"))); taskList.Add(Task.Run(() => Console.WriteLine($"子線程3,ThreadId: {Thread.CurrentThread.ManagedThreadId}"))); taskList.Add(Task.Run(() => Console.WriteLine($"子線程4,ThreadId: {Thread.CurrentThread.ManagedThreadId}"))); Task.WaitAny(taskList.ToArray());////阻塞當前線程,直到任一任務結束 Console.WriteLine($"有一個子線程執行完畢,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Task.WaitAll(taskList.ToArray());////阻塞當前線程,直到全部任務結束 Console.WriteLine($"所有子線程執行完畢,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"主線程結束,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Console.ReadKey(); } } }
執行結果:
主線程開始,ThreadId: 1
子線程1,ThreadId: 4
子線程2,ThreadId: 7
子線程3,ThreadId: 8
子線程4,ThreadId: 4
有一個子線程執行完畢,ThreadId: 1//阻塞了主線程執行!
所有子線程執行完畢,ThreadId: 1////阻塞了主線程執行!
主線程結束,ThreadId: 1
通過結果發現,無論怎么執行,最后三行輸出的順序都是一樣的!可見通過waitAny()、waitAll()方法可以阻塞主線程的執行,已經等達到一些順序控制的目的了!
注:1.盡量不要線程套線程,有更優秀的方法
2.子線程不能直接操作界面
等全部任務完成后啟動一個新的task完成后續動作
TaskFactory taskFactory =new TaskFactory();
taskFactory.ContinueWhenAll(taskList.ToArray(),tArray=>{
xxxx
})
等任一任務完成后啟動一個新的task完成后續動作
taskFactory.ContinueWhenAny(taskList.ToArray(),tArray=>{
xxxx
})
//continue的后續線程,不可能是主線程,其余皆可能
四.Parallel並行編程
主線程也參與計算,介於線程,節約一個線程
通過指定ParallelOptions控制最大並發數量
Parallel.Invoke(()=>{111;},()=>{222;},()=>{333;})
namespace ThreadTest { class Program { static void Main(string[] args) { Console.WriteLine($"主線程開始,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Parallel.Invoke(new ParallelOptions() { MaxDegreeOfParallelism = 4}, () => { Console.WriteLine($"子線程1,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); }, () => { Console.WriteLine($"子線程2,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000);}, () => { Console.WriteLine($"子線程3,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); }, () => { Console.WriteLine($"子線程4,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); }, () => { Console.WriteLine($"子線程5,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); }); Console.WriteLine($"主線程結束,ThreadId: {Thread.CurrentThread.ManagedThreadId}"); Console.ReadKey(); } } }
輸出結果:
主線程開始,ThreadId: 1
子線程1,ThreadId: 1
子線程2,ThreadId: 3
子線程3,ThreadId: 5
子線程4,ThreadId: 4
子線程5,ThreadId: 1
主線程結束,ThreadId: 1
特點:主線程(1)也參與計算,節約一個線程;可以通過ParallelOptions控制最大並發數量(本例設置為4),子線程5與線程1使用了同一個線程,所以5等1執行完了之后才會打印,同理主線程結束也在最后完成;
五、BeginInvoke()線程封裝相關的
委托.BeginInvoke()自動啟用異步多線程,並且帶 回調 BeginInvoke()
3.利用委托方式在子線程中改變主線程UI的內容
private delegate void delegateUpdateLabel(string text); private void UpdateLabel(string text) { if (this.updateProcess_label.InvokeRequired) { Invoke(new delegateUpdateLabel(UpdateLabel), new object[] { text }); } else { updateProcess_label.Text = text; } } IAsyncResult asyncResult1 = action.BeginInvoke("", null, null); IAsyncResult asyncResult2 = func.BeginInvoke(null, null);
判斷異步線程完成方式:
1.利用asyncResult.IsCompleted 2.利用信號量asyncResult.AsyncWaitHandle.WaitOne(),可以做超時判斷 ---waitOne(-1)一直等待 waitOne(100)等待100ms
帶返回值的異步調用方法 returnValue = func.EndInvoke(asyncResult2) EndInvoke()也能用在回調里,但只能用一次
TODO...