C#程式設計 執行緒,任務和同步(2) 開啟執行緒
建立執行緒的幾種方法:
1 非同步委託
建立執行緒的一種簡單方式是定義一個委託,並非同步呼叫它。 委託是方法的型別安全的引用。Delegate類 還支援非同步地呼叫方法。在後臺,Delegate類會建立一個執行任務的執行緒。
using System; using System.Collections.Generic; using System.Linq; using System.Net.NetworkInformation; using System.Text; using System.Threading; using System.Threading.Tasks; namespace _016_執行緒_委託方式發起執行緒 { class Program { //一般我們會為比較耗時的操作 開啟單獨的執行緒去執行,比如下載操作 static int Test(int i, string str) { Console.WriteLine("test:" + i + str); Thread.Sleep(100);//讓當親執行緒休眠(暫停執行緒的執行) 單位ms return 100; } static void Main(string[] args) {//在main執行緒中執行 一個執行緒裡面語句的執行 是從上到下的 // 通過委託 開啟一個執行緒 Func<int, string, int> a = Test; // 開啟一個新的執行緒去執行 a所引用的方法 IAsyncResult ar = a.BeginInvoke(100, " mytest", null, null); // IAsyncResult 可以取得當前執行緒的狀態 Console.WriteLine("main"); while (ar.IsCompleted == false)//如果當前執行緒沒有執行完畢 { Console.Write("."); Thread.Sleep(10); //控制子執行緒的檢測頻率 } int res = a.EndInvoke(ar);//取得非同步執行緒的返回值 Console.WriteLine(res); } } }
輸出結果:
上面是通過迴圈檢測判斷執行緒是否結束。當我們通過BeginInvoke開啟一個非同步委託的時候,返回的結果是IAsyncResult,我們可以通過它的AsyncWaitHandle屬性訪問等待控制代碼。這個屬性返回一個WaitHandler型別的物件,它中的WaitOne()方法可以等待委託執行緒完成其任務,WaitOne方法可以設定一個超時時間作為引數(要等待的最長時間),如果發生超時就返回false。
using System; using System.Collections.Generic; using System.Linq; using System.Net.NetworkInformation; using System.Text; using System.Threading; using System.Threading.Tasks; namespace _016_執行緒_委託方式發起執行緒 { class Program { //一般我們會為比較耗時的操作 開啟單獨的執行緒去執行,比如下載操作 static int Test(int i, string str) { Console.WriteLine("test:" + i + str); Thread.Sleep(100);//讓當親執行緒休眠(暫停執行緒的執行) 單位ms return 100; } static void Main(string[] args) {//在main執行緒中執行 一個執行緒裡面語句的執行 是從上到下的 // 通過委託 開啟一個執行緒 Func<int, string, int> a = Test; IAsyncResult ar = a.BeginInvoke(100, " mytest", null, null);// 開啟一個新的執行緒去執行 a所引用的方法 // IAsyncResult 可以取得當前執行緒的狀態 Console.WriteLine("main"); //檢測執行緒結束 bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);//1000毫秒錶示超時時間,如果等待了1000毫秒 執行緒還沒有結束的話 那麼這個方法會返回false 如果在1000毫秒以內執行緒結束了,那麼這個方法會返回true if (isEnd) { int res = a.EndInvoke(ar); Console.WriteLine(res); } } } }
等待委託的結果的第3種方式是使用非同步回撥。在BeginInvoke的第三個引數中,可以傳遞一個滿足AsyncCallback委託的方法,AsyncCallback委託定義了一個IAsyncResult型別的引數其返回型別是void。對於最後一個引數,可以傳遞任意物件,以便從回撥方法中訪問它。
using System; using System.Collections.Generic; using System.Linq; using System.Net.NetworkInformation; using System.Text; using System.Threading; using System.Threading.Tasks; namespace _016_執行緒_委託方式發起執行緒 { class Program { //一般我們會為比較耗時的操作 開啟單獨的執行緒去執行,比如下載操作 static int Test(int i, string str) { Console.WriteLine("test:" + i + str); Thread.Sleep(100);//讓當親執行緒休眠(暫停執行緒的執行) 單位ms return 100; } static void Main(string[] args) {//在main執行緒中執行 一個執行緒裡面語句的執行 是從上到下的 Console.WriteLine("main"); //通過回撥 檢測執行緒結束 Func<int, string, int> a = Test; // 倒數第二個引數是一個委託型別的引數,表示回撥函式,就是當執行緒結束的時候會呼叫這個委託指向的方法 倒數第一個引數用來給回撥函式傳遞資料 IAsyncResult ar = a.BeginInvoke(100, " mytest", OnCallBack, a); Console.ReadKey(); } static void OnCallBack( IAsyncResult ar ) { Func<int, string, int> a = ar.AsyncState as Func<int, string, int>; int res = a.EndInvoke(ar); Console.WriteLine(res+"在回撥函式中取得結果"); } } }
在第三種方法中,我們可以使用Lamba表示式
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace _016_執行緒_委託方式發起執行緒
{
class Program
{
//一般我們會為比較耗時的操作 開啟單獨的執行緒去執行,比如下載操作
static int Test(int i, string str)
{
Console.WriteLine("test:" + i + str);
Thread.Sleep(100);//讓當親執行緒休眠(暫停執行緒的執行) 單位ms
return 100;
}
static void Main(string[] args)
{
Console.WriteLine("main");
//通過回撥 檢測執行緒結束
Func<int, string, int> a = Test;
a.BeginInvoke(100, "siki", ar =>
{
int res = a.EndInvoke(ar);
Console.WriteLine(res + "在lambda表示式中取得");
}, null);
Console.ReadKey();
}
}
}
2 通過Thread類
我們可以通過Thread類來建立執行緒,我們構造一個thread物件的時候,可以傳遞一個靜態方法,也可以傳遞一個物件的普通方法。我們先傳遞一個靜態方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace _017_執行緒_通過Thread發起執行緒 {
class Program {
// 注意:執行緒呼叫的方法定義中,引數要用object
static void DownloadFile(object filename)
{
// Thread.CurrentThread.ManagedThreadId表示當前執行緒ID
Console.WriteLine("開始下載:" +Thread.CurrentThread.ManagedThreadId +filename);
Thread.Sleep(2000);
Console.WriteLine("下載完成");
}
static void Main(string[] args) {
//建立執行緒,傳入要執行的方法
Thread t = new Thread(DownloadFile);
// 開啟執行緒,如果執行緒呼叫的方法有引數,在Start中傳入
t.Start("test");
Console.WriteLine("Main");
Console.ReadKey();
// 使用Lamba表示式方法
//Thread t = new Thread(() =>
//{
// Console.WriteLine("開始下載:" + Thread.CurrentThread.ManagedThreadId);
// Thread.Sleep(2000);
// Console.WriteLine("下載完成");
//});
//t.Start();
}
}
}
輸出結果:
下面我們傳遞一個物件的普通方法,先定義一個類:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace _017_執行緒_通過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("下載完成");
}
}
}
然後建立執行緒:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace _017_執行緒_通過Thread發起執行緒 {
class Program {
static void Main(string[] args) {
MyThread my = new MyThread("xxx.bt","http://www.xxx.bbs");
Thread t = new Thread(my.DownFile);
t.Start();
}
}
}
3 使用執行緒池
建立執行緒需要時間。 如果有不同的小任務要完成,就可以事先建立許多執行緒 , 在應完成這些任務時發出請求。 這個執行緒數最好在需要更多的執行緒時增加,在需要釋放資源時減少。
不需要 自己建立執行緒池,系統已經有一個ThreadPool類管理執行緒。 這個類會在需要時增減池中執行緒的執行緒數,直到達到最大的執行緒數。 池中的最大執行緒數是可配置的。 在雙核 CPU中 ,預設設定為1023個工作執行緒和 1000個 I/o執行緒。也可以指定在建立執行緒池時應立即啟動的最小執行緒數,以及執行緒池中可用的最大執行緒數。 如果有更多的作業要處理,執行緒池中執行緒的個數也到了極限,最新的作業就要排隊,且必須等待執行緒完成其任務。
使用執行緒池需要注意的事項:
執行緒池中的所有執行緒都是後臺執行緒 。 如果程序的所有前臺執行緒都結束了,所有的後臺執行緒就會停止。 不能把入池的執行緒改為前臺執行緒 。
不能給入池的執行緒設定優先順序或名稱。
入池的執行緒只能用於時間較短的任務。 如果執行緒要一直執行(如 Word的拼寫檢查器執行緒),就應使用Thread類建立一個執行緒。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace _018_執行緒_執行緒池 {
class Program {
// 注意:使用執行緒池新增的執行函式,必須要有一個object型別的引數,即時不用
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);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
Console.ReadKey();
}
}
}
輸出結果:
4 通過任務建立
在.NET4 新的名稱空間System.Threading.Tasks包含了類抽象出了執行緒功能,在後臺使用的ThreadPool進行管理的。任務表示應完成某個單元的工作。這個工作可以在單獨的執行緒中執行,也可以以同步方式啟動一個任務。 任務也是非同步程式設計中的一種實現方式。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace _019_執行緒_任務 {
class Program {
static void ThreadMethod() {
Console.WriteLine("任務開始");
Thread.Sleep(2000);
Console.WriteLine("任務結束");
}
static void Main(string[] args) {
// 通過Task建立
Task t = new Task(ThreadMethod);
t.Start();
// 通過任務工廠建立
//TaskFactory tf = new TaskFactory();
//Task t = tf.StartNew(ThreadMethod);
Console.WriteLine("Main");
Console.ReadKey();
}
}
}
輸出結果:
連續任務:如果一個任務t1的執行是依賴於另一個任務t2的,那麼就需要在這個任務t2執行完畢後才開始執行t1。這個時候我們可以使用連續任務。
static void DoFirst(){
Console.WriteLine("do in task : "+Task.CurrentId);
Thread.Sleep(3000);
}
static void DoSecond(Task t){
Console.WriteLine("task "+t.Id+" finished.");
Console.WriteLine("this task id is "+Task.CurrentId);
Thread.Sleep(3000);
}
Task t1 = new Task(DoFirst);
Task t2 = t1.ContinueWith(DoSecond);
Task t3 = t1.ContinueWith(DoSecond);
Task t4 = t2.ContinueWith(DoSecond);
任務層次結構:我們在一個任務中啟動一個新的任務,相當於新的任務是當前任務的子任務,兩個任務非同步執行,如果父任務執行完了但是子任務沒有執行完,它的狀態會設定為WaitingForChildrenToComplete,只有子任務也執行完了,父任務的狀態就變成RunToCompletion
static void Main(){
var parent = new Task(ParentTask);
parent.Start();
Thread.Sleep(2000);
Console.WriteLine(parent.Status);
Thread.Sleep(4000);
Console.WriteLine(parent.Status);
Console.ReadKey();
}
static void ParentTask(){
Console.WriteLine("task id "+Task.CurrentId);
var child = new Task(ChildTask);
child.Start();
Thread.Sleep(1000);
Console.WriteLine("parent started child , parent end");
}
static void ChildTask(){
Console.WriteLine("child");
Thread.Sleep(5000);
Console.WriteLine("child finished ");