零、學習目錄:
1 進程-線程-多線程,同步和異步
2 委託啓動異步調用
3 多線程特點:不卡主線程、速度快、無序性
4 異步的回調和狀態參數
5 異步等待三種方式
6 異步返回值
一、進程-線程-多線程,同步和異步
1.什麼是進程?
當一個程序開始運行時,它就是一個進程,進程包括運行中的程序和程序所使用到的內存和系統資源。
而一個進程又是由多個線程所組成的。
2.什麼是線程?
線程是程序中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程序計數器等),但代碼區是共享的,即不同的線程可以執行同樣的函數。
3.什麼是多線程?
多線程是指程序中包含多個執行流,即在一個程序中可以同時運行多個不同的線程來執行不同的任務,也就是說允許單個程序創建多個並行執行的線程來完成各自的任務。
4.多線程的好處:
可以提高CPU的利用率。在多線程程序中,一個線程必須等待的時候,CPU可以運行其它的線程而不是等待,這樣就大大提高了程序的效率。
5.多線程的不利方面:
線程也是程序,所以線程需要佔用內存,線程越多佔用內存也越多;
多線程需要協調和管理,所以需要CPU時間跟蹤線程;
線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題;
線程太多會導致控制太複雜,最終可能造成很多Bug;6.何時使用多線程?
多線程程序一般被用來在後臺執行耗時的任務。主線程保持運行,並且工作線程做它的後臺工作。對於Windows Forms程序來說,如果主線程試圖執行冗長的操作,鍵盤和鼠標的操作會變的遲鈍,程序也會失去響應。由於這個原因,應該在工作線程中運行一個耗時任務時添加一個工作線程,即使在主線程上有一個有好的提示“處理中...”,以防止工作無法繼續。這就避免了程序出現由操作系統提示的“沒有相應”,來誘使用戶強制結束程序的進程而導致錯誤。模式對話框還允許實現“取消”功能,允許繼續接收事件,而實際的任務已被工作線程完成。BackgroundWorker恰好可以輔助完成這一功能。
在沒有用戶界面的程序裏,比如說Windows Service,多線程在當一個任務有潛在的耗時,因爲它在等待另臺電腦的響應(比如一個應用服務器,數據庫服務器,或者一個客戶端)的實現特別有意義。用工作線程完成任務意味着主線程可以立即做其它的事情。
另一個多線程的用途是在方法中完成一個複雜的計算工作。這個方法會在多核的電腦上運行的更快,如果工作量被多個線程分開的話(使用Environment.ProcessorCount屬性來偵測處理芯片的數量)。
一個C#程序稱爲多線程的可以通過2種方式:明確地創建和運行多線程,或者使用.NET framework的暗中使用了多線程的特性——比如BackgroundWorker類, 線程池,threading timer,遠程服務器,或Web Services或ASP.NET程序。在後面的情況,人們別無選擇,必須使用多線程;一個單線程的ASP.NET web server不是太酷,即使有這樣的事情;幸運的是,應用服務器中多線程是相當普遍的;唯一值得關心的是提供適當鎖機制的靜態變量問題。
7、何時不要使用多線程?
多線程也同樣會帶來缺點,最大的問題是它使程序變的過於複雜,擁有多線程本身並不複雜,複雜是的線程的交互作用,這帶來了無論是否交互是否是有意的,都會帶來較長的開發週期,以及帶來間歇性和非重複性的bugs。因此,要麼多線程的交互設計簡單一些,要麼就根本不使用多線程。除非你有強烈的重寫和調試慾望。
當用戶頻繁地分配和切換線程時,多線程會帶來增加資源和CPU的開銷。在某些情況下,太多的I/O操作是非常棘手的,當只有一個或兩個工作線程要比有衆多的線程在相同時間執行任務塊的多。稍後我們將實現生產者/耗費者隊列,它提供了上述功能。二、委託啓動異步調用
1.異步介紹:不會等待方法的完成,會直接進入下一行 非阻塞式
2.委託啓用:可以定義一個委託
#region Private Method
/// <summary>
/// 一個比較耗時耗資源的私有方法
/// </summary>
/// <param name="name"></param>
private void DoSomethingLong(string name)
{
Console.WriteLine($"****************DoSomethingLong {name} Start {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;
}
//Thread.Sleep(2000);
Console.WriteLine($"****************DoSomethingLong {name} End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
}
#endregion
3.異步調用:可以在方法裏異步調用委託
#region Async
/// <summary>
/// 異步方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAsync_Click(object sender, EventArgs e)
{
Console.WriteLine($"****************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
//{
// Action<string> action = this.DoSomethingLong;
// action.Invoke("btnAsync_Click_1");
// action("btnAsync_Click_2");
// //請 小明 吃飯,大叔說我很忙,然後我就等着你忙完,然後一起去吃飯 誠心誠意 同步方法
// //請 小黃 吃飯,你說你很忙,那我就去吃飯了,你忙完了,你自己去吃飯 客氣一下 異步方法
// action.BeginInvoke("btnAsync_Click_3", null, null);
//}
Action<string> action = this.DoSomethingLong;
for (int i = 0; i < 5; i++)
{
//Thread.Sleep(5);
string name = string.Format($"btnAsync_Click_{i}");
action.BeginInvoke(name, null, null);
}
Console.WriteLine($"****************btnAsync_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}
#endregion
三、多線程特點:不卡主線程、速度快、無序性
1.異步多線程方法不卡界面,主線程完事兒了,計算任務交給子線程在做;winform提升用戶體驗;web一個業務操作後要發郵件,異步發送郵件。
2.異步多線程方法快,因爲多個線程併發運算;並不是線性增長,a)資源換時間,可能資源不夠 b) 多線程也有管理成本;但並不是越多越好,多個獨立任務可以同時運行。
3.異步多線程無序:啓動無序 執行時間不確定 結束也無序,一定不要通過等幾毫秒的形式來控制啓動/執行時間/結束。
四、異步的回調和狀態參數
異步的回調是用BeginInvoke,這裏它的狀態參數有三個,第一個是文本信息,第二個是異步結果的狀態,第三個是用戶自定義的信息。
#region 異步多線程方法
private void button3_Click(object sender, EventArgs e)
{
Console.WriteLine($"****************異步多線程方法 開始 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
Action<string> action = this.DoSomethingLong;
IAsyncResult asyncResult = null;
AsyncCallback callback = new AsyncCallback(ia =>
{
Console.WriteLine(object.ReferenceEquals(asyncResult, ia));
Console.WriteLine(ia.AsyncState);
Console.WriteLine($"到這裏計算已經完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
}
);
asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "mantianxin"); Console.WriteLine($"全部計算真的都完成了,然後給用戶返回{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
Console.WriteLine($"****************異步多線程方法 結束 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}
#endregion
#region 一個比較耗時耗資源的私有方法
private void DoSomethingLong(string name)
{
Console.WriteLine($"****************DoSomethingLong {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 {name} 結束 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}
#endregion
五、異步等待三種方式
a).延遲等待;
b).即時等待;
c).異步調用,帶返回值;
1.延遲等待:我們平常看到的很多文件下載或者上傳有個進度條顯示,大多都是用到延遲等待。下面做一個簡單的例子模擬文件上傳的效果。
代碼如下:
#region 異步多線程方法
private void button3_Click(object sender, EventArgs e)
{
Console.WriteLine($"****************異步多線程方法 開始 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
Action<string> action = this.DoSomethingLong;
IAsyncResult asyncResult = null;
AsyncCallback callback = new AsyncCallback(ia =>
{
//Console.WriteLine(object.ReferenceEquals(asyncResult,ia));
//Console.WriteLine(ia.AsyncState);
//Console.WriteLine($"到這裏計算已經完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
}
);
asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "mantianxin");
// Console.WriteLine($"全部計算真的都完成了,然後給用戶返回{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
#region 模擬文件上傳,但是存在着延遲
int i = 0;
while (!asyncResult.IsCompleted)//表示異步操作有沒有完成,這裏是沒有完成;會卡界面:主線程忙於等待
{
if (i < 10)
{
Console.WriteLine($"文件上傳完成{i++ * 10}%...");//File.ReadSize
}
else
{
Console.WriteLine($"文件上傳完成99.9%...");
}
Thread.Sleep(200);
}
Console.WriteLine($"文件上傳成功!");
#endregion
Console.WriteLine($"全部計算真的都完成了,然後給用戶返回{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
Console.WriteLine($"****************異步多線程方法 結束 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}
#endregion
2.即時等待:即時等待可以防止頁面(UI)假死。
代碼如下:
#region 即時等待
Thread.Sleep(200);
Console.WriteLine("Do Something Else...");
Console.WriteLine("Do Something Else...");
Console.WriteLine("Do Something Else...");
//asyncResult.AsyncWaitHandle.WaitOne();//等待任務的完成!
//asyncResult.AsyncWaitHandle.WaitOne(-1);//等待任務的完成!
asyncResult.AsyncWaitHandle.WaitOne(1000);//限時等待,需求:請求超時功能!
#endregion
3.異步調用,帶返回值:可以在異步的時候,帶上想要的返回值
代碼如下:
#region 異步調用,帶返回值
{
Func<int> func = () =>
{
Thread.Sleep(200);
return DateTime.Now.Day;
};
Console.WriteLine($"func.Invoke()={func.Invoke()}");
//委託的回調(異步調用)
IAsyncResult async = func.BeginInvoke(n =>
{
Console.WriteLine(n.AsyncState);
}, "mantianxin");
Console.WriteLine($"func.EndInvoke(asyncResult)={func.EndInvoke(async)}");
}
#endregion
六、異步返回值