12 非同步多執行緒(二)Thread,ThreadPool,Task
一.Thread
1.Thread 是framework1.0時候就存在的,可以用TreadStart來啟動多執行緒。
Stopwatch watch = new Stopwatch();//計時器 watch.Start(); Console.WriteLine($"*******btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} *********"); ThreadStart threadStart = new ThreadStart(()=>this.DoSomethingLong("btnThreads_Click")); Thread thread = new Thread(threadStart); thread.Start(); watch.Stop(); Console.WriteLine($"*****btnThreads_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}*****");
private void DoSomethingLong(string name) { Console.WriteLine($"****************DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1000000000; i++) { lResult += i; } Console.WriteLine($"****************DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
執行結果是:
這裡插一句委託用lambda表示式的寫法:
//這裡為什麼要寫成 Action<string>,因為this.DoSomethingLong方法是有string引數的,Action要和它配套使用,Action的引數是in,表示傳入型別,所以寫成Action<string>
Action<string> action = this.DoSomethingLong;
還有這一句:
// ThreadStart是無參無返回值的,但是this.DoSomethingLong是需要傳一個string引數,這裡就有衝突了,要包一層 //包成()=>this.DoSomethingLong("btnThreads_Click"),就成了無參無返回值的委託 ThreadStart threadStart = new ThreadStart(()=>this.DoSomethingLong("btnThreads_Click"));
Thread還有一些其他的方法,比如:
thread.Suspend();//已經拋棄
thread.Resume();//已經拋棄
thread.Join();//做等待,現在用Thread時,也就這個方法靠譜了
thread.Abort();//已經拋棄
但是除去Join()方法,其他方法不靠譜,因為執行緒是作業系統分配的,也歸作業系統管理,這些方法不一定真正被執行了。
thread.Join()的效果:
介紹一個概念,前臺執行緒和後臺執行緒。
thread.Start();//預設是前臺執行緒,UI執行緒退出後,還會繼續執行完;後臺執行緒就直接退出了,只有Thread有前臺執行緒,其他Task、 Parallel都沒有。加上thread.IsBackground = true,變成後臺執行緒。
2.既然Thread是framwork1.0時代的,那時候還沒有呼叫回撥函式callback方法,所以需要自己寫。
private void ThreadWithCallback(ThreadStart threadStart,Action callback)
{
ThreadStart startNew = new ThreadStart(
() =>
{
threadStart.Invoke();
callback.Invoke();
});
Thread thread = new Thread(startNew);
thread.Start();
}
這樣寫,等於是threadStart.Invoke()執行完以後,再執行callback.Invoke(),它們在同一執行緒內是順序執行的,達到了回撥目的。
而測試程式碼可以這樣寫:
Stopwatch watch = new Stopwatch();
watch.Start();
Console.WriteLine($"****************btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
this.ThreadWithCallback(()=>
{
Thread.Sleep(2000);
Console.WriteLine($"這裡是ThreadStart {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
,()=>
{
Thread.Sleep(2000);
Console.WriteLine($"這裡是callback {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
);
watch.Stop();
Console.WriteLine($"****************btnThreads_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗時{watch.ElapsedMilliseconds}ms***************");
因為ThreadWithCallback方法的兩個引數:ThreadStart 和 Action都是委託,所以可以這樣寫。
執行結果:
同樣Thread是framwork1.0時代的,那時候還沒有得到返回值的EndInvoke方法,所以需要自己寫。
private Func<T> ThreadWithReturn<T>(Func<T> funcT)
{
T t = default(T);
//只有將funcT包到子執行緒裡面,才不會卡介面
ThreadStart startNew = new ThreadStart(
()=>
{
t = funcT.Invoke();
});
Thread thread = new Thread(startNew);
thread.Start();
return new Func<T>(()=>
{
//這裡既然要拿到返回值,就要等待執行緒計算完,所以要Join
thread.Join();
return t;
});
}
測試程式碼:
Stopwatch watch = new Stopwatch();
watch.Start();
Console.WriteLine($"****************btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
Func<int> func = this.ThreadWithReturn(() =>//begininvoke
{
Thread.Sleep(2000);
Console.WriteLine($"這裡是ThreadStart {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
return 12345;
});
Console.WriteLine("已經執行到這裡了。。。");
int iResult = func.Invoke();//endinvoke
watch.Stop();
Console.WriteLine($"****************btnThreads_Click End iResult={iResult}, {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗時{watch.ElapsedMilliseconds}ms***************");
執行結果:
二.ThreadPool
ThreadPool是在framework2.0出現的,它避免了Thread給使用者的權利太強大,做了限制,只有一個QueueUserWorkItem方法來啟動執行緒。例如:
ThreadPool.QueueUserWorkItem(o =>
{
Thread.Sleep(5000);
this.DoSomethingLong("btnThreads_Click1");
});
ThreadPool的好處是:
1 去掉各種 api 避免濫用,降低複雜度
2 池化:1)減少建立/銷燬的成本 2 )限制最大執行緒數量
什麼是池化?池化就是執行緒池一開始就向作業系統申請了一些程序,準備好了放線上程池中。當程式需要程序時,向執行緒池要,用完之後,自己也不用銷燬,直接送還給執行緒池,執行緒池的操作是framework底層做的,不需要人工干預,所以提供給使用者呼叫的方法很少。
ManualResetEvent 它是一個訊號值,可以像WaitOne搭配Thread一樣,搭配ThreadPool使用。
ManualResetEvent mre = new ManualResetEvent(false);//false 關閉
new Action(() =>
{
Thread.Sleep(5000);
Console.WriteLine("委託的非同步呼叫");
mre.Set();//開啟
}).BeginInvoke(null, null);
//只有上面子執行緒執行完,mrc.Set()重新開啟,才可以執行下面的方法
mre.WaitOne();
Console.WriteLine("12345");
mre.Reset();//關閉
new Action(() =>
{
Thread.Sleep(5000);
Console.WriteLine("委託的非同步呼叫2");
mre.Set();//開啟
}).BeginInvoke(null, null);
mre.WaitOne();
Console.WriteLine("23456");
但是,如果沒有這個需求,就不要等待,阻塞執行緒,很容易死鎖。最好用回撥。
三.Task
目前最推薦的就是Task,它是framework3.0時候出來的,API的功能很強大。執行緒都來自執行緒池,所以都是後臺執行緒。
1.WaitAll和WaitAny:這兩者是等待。
WaitAll:主執行緒等待所有子執行緒執行完才返回,所以會卡介面
WaitAny:某個子執行緒執行完就返回。比如要獲取資料,可以從介面,可以從資料庫,可以從檔案,那麼,只要從一個途徑拿到了資料,就返回,其他途徑的結果可以不要了,類似於這種只需要一個結果的場景,就可以用WaitAny。
TaskFactory taskFactory = Task.Factory;// new TaskFactory();
都說List<Task> taskList = new List<Task>();
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_002")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_001")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_003")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_004")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_005")));
Task.WaitAny(taskList.ToArray());//卡介面
Console.WriteLine("某個任務都完成,才會執行");
Task.WaitAll(taskList.ToArray());//卡介面
Console.WriteLine("全部任務都完成,才會執行");
WaitAll和WaitAny,都是卡介面的,有沒有不卡介面的呢?
2.ContinueWhenAll 和 ContinueWhenAny :這兩者是回撥。
一個是等待子執行緒全部完成,一個是等待某一個子執行緒完成,然後再開一個新的子執行緒執行下面的操作,它們都是回撥。
TaskFactory taskFactory = Task.Factory;// new TaskFactory();
List<Task> taskList = new List<Task>();
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_002")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_001")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_003")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_004")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_005")));
//回撥
taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"ContinueWhenAny {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
taskFactory.ContinueWhenAll(taskList.ToArray(), tList => Console.WriteLine($"這裡是ContinueWhenAll {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
總結一下,WaitAll,WaitAny,ContinueWhenAll , ContinueWhenAny,有了這2個等待和2個回撥的方法,可以解決絕大部分多執行緒執行的控制順序的問題。
還有帶返回值的(Func):
Task<int> intTask = taskFactory.StartNew(() => 123);
int iResult = intTask.Result;
還有單獨一個多執行緒執行後的回撥:
// "煎餅果子"就是t.AsyncState
Task task = taskFactory.StartNew(t => this.DoSomethingLong("btnTask_Click_005"), "煎餅果子")
.ContinueWith(t => Console.WriteLine($"這裡是{t.AsyncState}的回撥"));
千萬不要在Task裡面去啟動Task