在計算機中,一個線程就是一系列的命令,一個工做單元。操做系統能夠管理多個線程,給每一個線程分配cpu執行的時間片,而後切換不一樣的線程在這個cpu上執行。這種單核的處理器一次只能作一件事,不能同時作兩件以上的事情,只是經過時間的分配來實現多個線程的執行。可是在多核處理器上,能夠實現同時執行多個線程。操做系統能夠將時間分配給第一個處理器上的線程,而後在另外一個處理器上分配時間給另外一個線程。編程
異步是相對於同步而言。跟多線程不能同一而論。服務器
異步編程採用future或callback機制,以免產生沒必要要的線程。(一個future表明一個將要完成的工做。)異步編程核心就是:啓動了的操做將在一段時間後完成。這個操做正在執行時,不會阻塞原來的線程。啓動了這個操做的線程,能夠繼續執行其餘任務。當操做完成時,會通知它的future或者回調函數,以便讓程序知道操做已經結束。多線程
爲何要使用異步:異步
面向終端用戶的GUI程序:異步編程提升了相應能力。可使程序在執行任務時仍能相應用戶的輸入。
服務器端應用:實現了可擴展性。服務器應用能夠利用線程池知足其可擴展性。async
若是以同步方式執行某個任務時,須要等待該任務完成,而後才能再繼續執行另外一個任務。而用異步執行某個任務時,能夠在該任務完成以前執行另外一個任務。異步最重要的體現就是不排隊,不阻塞。異步編程
圖:單線程同步
函數
圖:多線程同步
操作系統
異步能夠在單個線程上實現,也能夠在多個線程上實現,還能夠不須要線程(一些IO操做)。線程
圖:單線程異步
3d
圖:多線程異步
異步能夠分爲CPU異步和IO異步。異步在CPU操做中是必需要跑在線程上的,通常狀況下這時咱們都會新開一個線程執行這個異步操做。但在IO操做中是不須要線程的,硬件直接和內存操做。
可是是否建立線程取決於你的異步的實現方式。好比在異步你用ThreadPool,Task.Run()等方法是建立了一個線程池的線程,那麼該異步是在另外一個線程上執行。
C#實現異步的四種方式:
Task
的異步async
,await
關鍵字異步異步模式是調用Beginxxx
方法,返回一個IAsyncResult
類型的值,在回調函數裏調用Endxxxx(IAsyncResult)
獲取結果值。
異步模式中最多見的是委託的異步。
如:聲明一個string類型輸入參數和string類型返回值的委託。調用委託的BeginInvoke方法,來異步執行該委託。
Func<string, string> func = (string str) => { Console.WriteLine(str); return str + " end"; }; func.BeginInvoke("hello",IAsyncResult ar => { Console.WriteLine(func.EndInvoke(ar)); }, null); //輸出: //hello //hello end
BeginInvoke
方法的第一個參數表示委託的輸入參數。
第二個參數表示IAsyncResult
類型輸入參數的回調函數,其實也是個委託。
第三個參數是個狀態值。
事件異步有一個xxxAsync
方法,和對應該方法的 xxxCompleted
事件。
如: backgroundworker
和progressbar
結合
public partial class MainWindow : Window { private BackgroundWorker bworker = new BackgroundWorker(); public MainWindow() { InitializeComponent(); //支持報告進度 bworker.WorkerReportsProgress = true; //執行具體的方法 bworker.DoWork += Bworker_DoWork; //進度變化時觸發的事件 bworker.ProgressChanged += Bworker_ProgressChanged; //異步結束時觸發的事件 bworker.RunWorkerCompleted += Bworker_RunWorkerCompleted; Loaded += MainWindow_Loaded; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { //開始異步執行 bworker.RunWorkerAsync(); } private void Bworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //異步完成時觸發的事件 progressBar.value=100; } private void Bworker_ProgressChanged(object sender, ProgressChangedEventArgs e) { //獲取進度值複製給progressBar progressBar.Value = e.ProgressPercentage; } private void Bworker_DoWork(object sender, DoWorkEventArgs e) { for (int j = 0; j <= 100; j++) { //調用進度變化方法,觸發進度變化事件 bworker.ReportProgress(j); Thread.Sleep(100); } } }
Task
是在Framework4.0提出來的新概念。Task
自己就表示一個異步操做(Task
默認是運行在線程池裏的線程上)。它比線程更輕量,能夠更高效的利用線程。而且任務提供了更多的控制操做。
任務的執行默認是由任務調度器來實現的(任務調用器使這些任務並行執行)。任務的執行和線程不是一一對應的。有可能會是幾個任務在同一個線程上運行,充分利用了線程,避免一些短期的操做單獨跑在一個線程裏。因此任務更適合CPU密集型操做。
任務能夠賦值當即運行,也能夠先由構造函數賦值,以後再調用。
//啓用線程池中的線程異步執行 Task t1 = Task.Factory.StartNew(() => { Console.WriteLine("Task啓動..."); }); //啓用線程池中的線程異步執行 Task t2 = Task.Run(() => { Console.WriteLine("Task啓動..."); }); Task t3 = new Task(() => { Console.WriteLine("Task啓動..."); }); t3.Start();//啓用線程池中的線程異步執行 t3.RunSynchronously();//任務同步執行
Task t1 = Task.Run(() => { Console.WriteLine("Task啓動..."); }); Task t2 = Task.Run(() => { Console.WriteLine("Task啓動..."); }); //調用WaitAll() ,會阻塞調用線程,等待任務執行完成 ,這時異步也沒有意義了 Task.WaitAll(new Task[] { t1, t2 }); Console.WriteLine("Task完成..."); //調用ContinueWith,等待任務完成,觸發下一個任務,這個任務可看成任務完成時觸發的回調函數。 //爲了獲取結果,同時不阻塞調用線程,建議使用ContinueWith,在任務完成後,接着執行一個處理結果的任務。 t1.ContinueWith((t) => { Console.WriteLine("Task完成..."); }); t2.ContinueWith((t) => { Console.WriteLine("Task完成..."); }); //調用GetAwaiter()方法,獲取任務的等待者,調用OnCompleted事件,當任務完成時觸發 //調用OnCompleted事件也不會阻塞線程 t1.GetAwaiter().OnCompleted(() => { Console.WriteLine("Task完成..."); }); t2.GetAwaiter().OnCompleted(() => { Console.WriteLine("Task完成..."); });
//實例化一個取消實例 var source = new CancellationTokenSource(); var token = source.Token; Task t1 = Task.Run(() => { Thread.Sleep(2000); //判斷是否任務取消 if (token.IsCancellationRequested) { //token.ThrowIfCancellationRequested(); Console.WriteLine("任務已取消"); } Thread.Sleep(500); //token傳遞給任務 }, token); Thread.Sleep(1000); Console.WriteLine(t1.Status); //取消該任務 source.Cancel(); Console.WriteLine(t1.Status);
Task<string> t1 = Task.Run(() => TaskMethod("hello")); t1.Wait(); Console.WriteLine(t1.Result); public string TaskMethod(string str) { return str + " from task method"; }
Task異步操做,須要注意的一點就是調用Waitxxx方法,會阻塞調用線程。
首先要明確一點的就是async
await
不會建立線程。而且他們是一對關鍵字,必須成對的出現。
若是await
的表達式沒有建立新的線程,那麼一個異步操做就是在調用線程的時間片上執行,不然就是在另外一個線程上執行。
async Task MethodAsync() { Console.WriteLine("異步執行"); await Task.Delay(4000); Console.WriteLine("異步執行結束"); }
一個異步方法必須有async
修飾,且方法名以Async結尾。異步方法體至少包含一個await
表達式。await
能夠看做是一個掛起異步方法的一個點,且同時把控制權返回給調用者。異步方法的返回值必須是Task
或者Task<T>
。即若是方法沒有返回值那就用Task表示,若是有一個string類型的返回值,就用Task泛型Task<string>
修飾。
異步方法執行流程:
Task.Delay
結束,await
表達式結束,MehtodAsync執行await表達式以後的語句,輸出「異步執行結束」。和其餘方法同樣,async方法開始時以同步方式執行。在async內部,await關鍵字對它的參數執行一個異步等待。它首先檢查操做是否已經完成,若是完成了,就繼續運行(同步方式)。不然它會暫停async方法,並返回,留下一個未完成的Task。一段時間後,操做完成,async方法就恢復運行。
一個async方法是由多個同步執行的程序塊組成的,每一個同步程序塊之間由await語句分隔。第一個同步程序塊是在調用這個方法的線程中執行,但其餘同步程序塊在哪裏運行呢?狀況比較複雜。
最多見的狀況是用await語句等待一個任務完成,當該方法在await處暫停時,就能夠捕獲上下文(context)。若是當前SynchronizationContext不爲空,這個上下文就是當前SynchronizationContext。若是爲空,則這個上下文爲當前TaskScheduler。該方法會在這個上下文中繼續運行。通常來講,運行在UI線程時採用UI上下文,處理Asp.Net請求時採用Asp.Net請求上下文,其餘不少狀況下則採用線程池上下文。
由於,在上面的代碼中,每一個同步程序塊會試圖在原始的上下文中恢復運行。若是在UI線程調用async方法,該方法的每一個同步程序塊都將在此UI線程上運行。可是,若是在線程池中調用,每一個同步程序塊將在線程池上運行。
若是要避免這種行爲,能夠在await中使用configureAwait方法,將參數ContinueOnCapturedContext設置爲false。async方法中await以前的代碼會在調用的線程裏運行。在被await暫停後,await以後的代碼則會在線程池裏繼續運行。
async Task MethodAsync() { Console.WriteLine("異步執行");//同步程序塊1 await Task.Delay(4000).ConfigureAwait(false); Console.WriteLine("異步執行結束");//同步程序塊2 }
咱們可能想固然的認爲Task.Delay
會阻塞執行線程,就跟Thread.Sleep
同樣。其實他們是不同的。Task.Delay
建立一個將在設置時間後執行的任務。就至關於一個定時器,多少時間後再執行操做。不會阻塞執行線程。
當咱們在異步線程中調用Sleep的時候,只會阻塞異步線程。不會阻塞到主線程。
async Task Method2Async() { Console.WriteLine("await執行前..."+Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { Console.WriteLine("await執行..." + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); Console.WriteLine("await執行結束..." + Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("await以後執行..."+ Thread.CurrentThread.ManagedThreadId); } //輸出: //await執行前...9 //await執行...12 //await以後執行...9 //await執行結束...12
上面的異步方法,Task
建立了一個線程池線程,Thread.Sleep執行在線程池線程中。
參考: