詳解C#中 Thread,Task,Async/Await,IAsyncResult的那些事兒
說起非同步,Thread,Task,async/await,IAsyncResult 這些東西肯定是繞不開的,今天就來依次聊聊他們
1.執行緒(Thread)
多執行緒的意義在於一個應用程式中,有多個執行部分可以同時執行;對於比較耗時的操作(例如io,資料庫操作),或者等待響應(如WCF通訊)的操作,可以單獨開啟後臺執行緒來執行,這樣主執行緒就不會阻塞,可以繼續往下執行;等到後臺執行緒執行完畢,再通知主執行緒,然後做出對應操作!
在C#中開啟新執行緒比較簡單
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static void Main( string [] args)
{
Console.WriteLine( "主執行緒開始" );
//IsBackground=true,將其設定為後臺執行緒
Thread t = new Thread(Run) { IsBackground = true };
t.Start();
Console.WriteLine( "主執行緒在做其他的事!" );
//主執行緒結束,後臺執行緒會自動結束,不管有沒有執行完成
//Thread.Sleep(300);
Thread.Sleep(1500);
Console.WriteLine( "主執行緒結束" );
}
static void Run()
{
Thread.Sleep(700);
Console.WriteLine( "這是後臺執行緒呼叫" );
}
|
執行結果如下圖,
可以看到在啟動後臺執行緒之後,主執行緒繼續往下執行了,並沒有等到後臺執行緒執行完之後。
1.1 執行緒池
試想一下,如果有大量的任務需要處理,例如網站後臺對於HTTP請求的處理,那是不是要對每一個請求建立一個後臺執行緒呢?顯然不合適,這會佔用大量記憶體,而且頻繁地建立的過程也會嚴重影響速度,那怎麼辦呢?執行緒池就是為了解決這一問題,把建立的執行緒存起來,形成一個執行緒池(裡面有多個執行緒),當要處理任務時,若執行緒池中有空閒執行緒(前一個任務執行完成後,執行緒不會被回收,會被設定為空閒狀態),則直接呼叫執行緒池中的執行緒執行(例asp.net處理機制中的Application物件),
使用事例:
1 2 3 4 5 6 7 8 |
for ( int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(m =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
});
}
Console.Read();
|
執行結果:
可以看到,雖然執行了10次,但並沒有建立10個執行緒。
1.2 訊號量(Semaphore)
Semaphore負責協調執行緒,可以限制對某一資源訪問的執行緒數量
這裡對SemaphoreSlim類的用法做一個簡單的事例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多隻能有三個執行緒同時訪問
static void Main( string [] args)
{
for ( int i = 0; i < 10; i++)
{
new Thread(SemaphoreTest).Start();
}
Console.Read();
}
static void SemaphoreTest()
{
semLim.Wait();
Console.WriteLine( "執行緒" + Thread.CurrentThread.ManagedThreadId.ToString() + "開始執行" );
Thread.Sleep(2000);
Console.WriteLine( "執行緒" + Thread.CurrentThread.ManagedThreadId.ToString() + "執行完畢" );
semLim.Release();
}
|
執行結果如下:
可以看到,剛開始只有三個執行緒在執行,當一個執行緒執行完畢並釋放之後,才會有新的執行緒來執行方法!
除了SemaphoreSlim類,還可以使用Semaphore類,感覺更加靈活,感興趣的話可以搜一下,這裡就不做演示了!
2.Task
Task是.NET4.0加入的,跟執行緒池ThreadPool的功能類似,用Task開啟新任務時,會從執行緒池中呼叫執行緒,而Thread每次例項化都會建立一個新的執行緒。
1 2 3 4 5 6 7 8 9 10 11 |
Console.WriteLine( "主執行緒啟動" );
//Task.Run啟動一個執行緒
//Task啟動的是後臺執行緒,要在主執行緒中等待後臺執行緒執行完畢,可以呼叫Wait方法
//Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task啟動"); });
Task task = Task.Run(() => {
Thread.Sleep(1500);
Console.WriteLine( "task啟動" );
});
Thread.Sleep(300);
task.Wait();
Console.WriteLine( "主執行緒結束" );
|
執行結果如下:
開啟新任務的方法:Task.Run()或者Task.Factory.StartNew(),開啟的是後臺執行緒
要在主執行緒中等待後臺執行緒執行完畢,可以使用Wait方法(會以同步的方式來執行)。不用Wait則會以非同步的方式來執行。
比較一下Task和Thread:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static void Main( string [] args)
{
for ( int i = 0; i < 5; i++)
{
new Thread(Run1).Start();
}
for ( int i = 0; i < 5; i++)
{
Task.Run(() => { Run2(); });
}
}
static void Run1()
{
Console.WriteLine( "Thread Id =" + Thread.CurrentThread.ManagedThreadId);
}
static void Run2()
{
Console.WriteLine( "Task呼叫的Thread Id =" + Thread.CurrentThread.ManagedThreadId);
}
|
執行結果:
可以看出來,直接用Thread會開啟5個執行緒,用Task(用了執行緒池)開啟了3個!
2.1 Task<TResult>
Task<TResult>就是有返回值的Task,TResult就是返回值型別。
1 2 3 4 5 6 7 8 9 |
Console.WriteLine( "主執行緒開始" );
//返回值型別為string
Task< string > task = Task< string >.Run(() => {
Thread.Sleep(2000);
return Thread.CurrentThread.ManagedThreadId.ToString();
});
//會等到task執行完畢才會輸出;
Console.WriteLine(task.Result);
Console.WriteLine( "主執行緒結束" );
|
執行結果:
通過task.Result可以取到返回值,若取值的時候,後臺執行緒還沒執行完,則會等待其執行完畢!
簡單提一下:
Task任務可以通過CancellationTokenSource類來取消,感覺用得不多,用法比較簡單,感興趣的話可以搜一下!
3. async/await
async/await是C#5.0中推出的,先上用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
static void Main( string [] args)
{
Console.WriteLine( "-------主執行緒啟動-------" );
Task< int > task = GetStrLengthAsync();
Console.WriteLine( "主執行緒繼續執行" );
Console.WriteLine( "Task返回的值" + task.Result);
Console.WriteLine( "-------主執行緒結束-------" );
}
static async Task< int > GetStrLengthAsync()
{
Console.WriteLine( "GetStrLengthAsync方法開始執行" );
//此處返回的<string>中的字串型別,而不是Task<string>
string str = await GetString();
Console.WriteLine( "GetStrLengthAsync方法執行結束" );
return str.Length;
}
static Task< string > GetString()
{
//Console.WriteLine("GetString方法開始執行")
return Task< string >.Run(() =>
{
Thread.Sleep(2000);
return "GetString的返回值" ;
});
}
|
async用來修飾方法,表明這個方法是非同步的,宣告的方法的返回型別必須為:void,Task或Task<TResult>。
await必須用來修飾Task或Task<TResult>,而且只能出現在已經用async關鍵字修飾的非同步方法中。通常情況下,async/await成對出現才有意義,
看看執行結果:
可以看出來,main函式呼叫GetStrLengthAsync方法後,在await之前,都是同步執行的,直到遇到await關鍵字,main函式才返回繼續執行。
那麼是否是在遇到await關鍵字的時候程式自動開啟了一個後臺執行緒去執行GetString方法呢?
現在把GetString方法中的那行註釋加上,執行的結果是:
大家可以看到,在遇到await關鍵字後,沒有繼續執行GetStrLengthAsync方法後面的操作,也沒有馬上反回到main函式中,而是執行了GetString的第一行,以此可以判斷await這裡並沒有開啟新的執行緒去執行GetString方法,而是以同步的方式讓GetString方法執行,等到執行到GetString方法中的Task<string>.Run()的時候才由Task開啟了後臺執行緒!
那麼await的作用是什麼呢?
可以從字面上理解,上面提到task.wait可以讓主執行緒等待後臺執行緒執行完畢,await和wait類似,同樣是等待,等待Task<string>.Run()開始的後臺執行緒執行完畢,不同的是await不會阻塞主執行緒,只會讓GetStrLengthAsync方法暫停執行。
那麼await是怎麼做到的呢?有沒有開啟新執行緒去等待?
只有兩個執行緒(主執行緒和Task開啟的執行緒)!至於怎麼做到的(我也不知道......>_<),大家有興趣的話研究下吧!
4.IAsyncResult
IAsyncResult自.NET1.1起就有了,包含可非同步操作的方法的類需要實現它,Task類就實現了該介面
在不借助於Task的情況下怎麼實現非同步呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
class Program
{
static void Main( string [] args)
{
Console.WriteLine( "主程式開始--------------------" );
int threadId;
AsyncDemo ad = new AsyncDemo();
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);
IAsyncResult result = caller.BeginInvoke(3000, out threadId, null , null );
Thread.Sleep(0);
Console.WriteLine( "主執行緒執行緒 {0} 正在執行." ,Thread.CurrentThread.ManagedThreadId)
//會阻塞執行緒,直到後臺執行緒執行完畢之後,才會往下執行
result.AsyncWaitHandle.WaitOne();
Console.WriteLine( "主程式在做一些事情!!!" );
//獲取非同步執行的結果
string returnValue = caller.EndInvoke( out threadId, result);
//釋放資源
result.AsyncWaitHandle.Close();
Console.WriteLine( "主程式結束--------------------" );
Console.Read();
}
}
public class AsyncDemo
{
//供後臺執行緒執行的方法
public string TestMethod( int callDuration, out int threadId)
{
Console.WriteLine( "測試方法開始執行." );
Thread.Sleep(callDuration);
threadId = Thread.CurrentThread.ManagedThreadId;
return String.Format( "測試方法執行的時間 {0}." , callDuration.ToString());
}
}
public delegate string AsyncMethodCaller( int callDuration, out int threadId);
|
關鍵步驟就是紅色字型的部分,執行結果:
和Task的用法差異不是很大!result.AsyncWaitHandle.WaitOne()就類似Task的Wait。
5.Parallel
最後說一下在迴圈中開啟多執行緒的簡單方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Stopwatch watch1 = new Stopwatch();
watch1.Start();
for ( int i = 1; i <= 10; i++)
{
Console.Write(i + "," );
Thread.Sleep(1000);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed);
Stopwatch watch2 = new Stopwatch();
watch2.Start();
//會呼叫執行緒池中的執行緒
Parallel.For(1, 11, i =>
{
Console.WriteLine(i + ",執行緒ID:" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000);
});
watch2.Stop();
Console.WriteLine(watch2.Elapsed);
|
執行結果:
迴圈List<T>:
1 2 3 4 5 6 |
List< int > list = new List< int >() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 };
Parallel.ForEach< int >(list, n =>
{
Console.WriteLine(n);
Thread.Sleep(1000);
});
|
執行Action[]數組裡面的方法:
1 2 3 4 5 6 7 8 9 |
Action[] actions = new Action[] {
new Action(()=>{
Console.WriteLine( "方法1" );
}),
new Action(()=>{
Console.WriteLine( "方法2" );
})
};
Parallel.Invoke(actions);
|
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支援指令碼之家!