非同步、多執行緒、Await/Async、Task
非同步多執行緒經常被拿來說事,網上文章也是多如牛毛,我也是停留在很菜的水平,痛下決心好好“惡補”一下這塊知識。
還是先放兩個官方文件壓壓驚:使用 Async 和 Await 的非同步程式設計.NET 中的並行處理、併發和非同步程式設計
學習前:
耗時操作知道搞個Task.Run,不用等待結果就後臺執行,Api直接返回,多個任務就迴圈啟用Task。控制器方法加上async,呼叫加上await,別人這麼用也跟著,不造為毛。Thread最常用的是Sleep。。。就這樣。
學習後:
多執行緒的前世今生就不具體介紹了,簡單粗暴的實操文章奉上:Thread、ThreadPool、Task、Parallel的基本用法、區別以及弊端
通過幾個小例子Get不明白的知識點,搞一個winform,啟動的同時再帶一個控制檯,方便輸出對比,Program.cs需要加幾行程式碼。大概放了這幾個按鈕,寫了幾個輸出和呼叫非同步的方法(完整程式碼在最後)。
1、同步
這就不說了,睡3秒順序執行,有一點注意是文字框資訊是沒有即時更新的,winform正常情況下是主執行緒更新控制元件狀態的,因為主執行緒被阻塞了,所以無法重新整理UI,主執行緒釋放後才執行。
2、非同步中更新UI
Over的時候可以發現更新資訊是由執行緒id為5的執行緒完成的,跨執行緒操作winform控制元件的方法交給委託,見紅框。
3、非同步結果更新UI
如果非同步執行緒需要等待返回值可以這樣寫更簡潔,方法上加上了async關鍵字,await等待執行緒執行,那麼問題來了,非同步不就又變成同步了?意義何在呢?這也是實操前一直不明白的點,這個例子看不出效果,接著操作。
4、呼叫非同步
呼叫非同步方法,自己本身也應該是非同步的,如果沒有async關鍵字,就會報出警告,並且沒有async修飾的方法不能使用await關鍵字,這個例子只是把非同步方法MethodAsync拿出去了,為了演示下面的例子使用。
5、非同步呼叫死鎖
這裡呼叫非同步方法MethodAsyncDeadlock,與MethodAsync的區別就是,把耗時函式的結果返回了,由於主執行緒沒有等待非同步呼叫返回,又輸出了非同步方法的結果,自然是拿不到返回值,因為非同步方法還沒有執行完成,所以這執行緒就“幹架”了,卡這了,死鎖。
6、解決死鎖
死鎖大部分是程式碼不規範造成的,程式碼寫懵逼也是常有的事,解決也很簡單,await出馬,等非同步方法完成返回再繼續執行就OK了。
7、Thread、Parallel、Task
啟動方式直接看程式碼,有一個小坑需要注意,就是多執行緒訪問公共變數問題。
8、重點來了!!!
非同步程式設計的意義何在這個哲學問題,換一個控制檯例子更能說明問題。很簡單,一個控制檯、一個類。從執行結果可以看出來,Main方法開始,到MethodAsync非同步方法執行,開啟Task耗時函式之前都是執行緒1在幹活兒,耗時函式由另外一個執行緒4執行,開始之前執行緒1已已經返回,並且輸出了Main End,這意味著剩下的不管是耗時函式,還是等待耗時函式完成之後的邏輯,都不在需要主執行緒等待完成,由別的執行緒搞定。winform解釋不來是因為非同步完成之後還是交給了主執行緒UI去繼續執行了,所以執行緒id總是前後一致。
9、結論!!!
實操分析完之後,就明白了之前忘記在哪看到的一句話了:非同步能增加網站的吞吐量,但並不會讓網站變得更快。
1、提高網站的響應能力,也是async/await也常見於Web專案的原因。
2、可以用同步的寫法,完成非同步操作,需要的時候啟動執行緒並行執行,提高運算或邏輯處理能力,又能等待控制順序,可以說賊爽。
參考了:C# 徹底搞懂async/await,也是言簡意賅的文章。
附上程式碼(粗陋勿噴)
public partial class AsyncDome : Form
{
public AsyncDome()
{
InitializeComponent();
}
//同步
private void button1_Click(object sender, EventArgs e)
{
btnClickAop(sender, "Start", true);
Thread.Sleep(3000);
btnClickAop(sender, "End");
}
//非同步中更新UI
private void button2_Click(object sender, EventArgs e)
{
btnClickAop(sender, "Start", true);
Task.Run(() =>
{
Thread.Sleep(3000);
showConsoleDelegate(bt(sender) + " Over");
});
btnClickAop(sender, "End");
}
//非同步結果更新UI
private async void button3_Click(object sender, EventArgs e)
{
btnClickAop(sender, "Start", true);
var t = Task.Run(() =>
{
Thread.Sleep(3000);
return bt(sender) + " Over";
});
showConsole(await t);
btnClickAop(sender, "End");
}
//呼叫非同步
private void button4_Click(object sender, EventArgs e)
{
btnClickAop(sender, "Start", true);
MethodAsync();
btnClickAop(sender, "End");
}
//非同步呼叫死鎖
private void button5_Click(object sender, EventArgs e)
{
btnClickAop(sender, "Start", true);
var ResultTask = MethodAsyncDeadlock();
showConsole(ResultTask.Result); //死鎖原因:此行程式碼與呼叫方法搶佔主執行緒,導致死鎖。
btnClickAop(sender, "End");
}
//解決死鎖
private async void button6_Click(object sender, EventArgs e)
{
btnClickAop(sender, "Start", true);
var ResultTask = MethodAsyncDeadlock();
showConsole(await ResultTask); //死鎖解決:等待呼叫方法使用完成主執行緒即可。
btnClickAop(sender, "End");
}
//Thread迴圈
private void button7_Click(object sender, EventArgs e)
{
//Thread
for (int i = 0; i < 100; i++)
{
int newIndex = i;
new Thread(new ThreadStart(() => showConsoleDelegate("for: " + newIndex))).Start();
}
}
//Parallel迴圈
private void button8_Click(object sender, EventArgs e)
{
//Parallel
Parallel.ForEach<int>(new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, (str) =>
{
showConsoleDelegate(str.ToString());
});
}
//Task迴圈
private void button9_Click(object sender, EventArgs e)
{
//Task
for (int i = 0; i < 100; i++)
{
int newIndex = i;
Task.Run(() => { showConsoleDelegate("for: " + newIndex); });
}
}
#region 輸出處理
private void btnClickAop(object sender, string flag, bool empty = false)
{
if (empty)
{
txtConsloe.Text = "";
Console.Clear();
}
showConsole(bt(sender) + " " + flag);
}
private void showConsole(string info)
{
info = getInfo(info);
txtConsloe.Text += info;
Console.WriteLine(info);
}
private void showConsoleDelegate(string info)
{
info = getInfo(info);
Console.WriteLine(info);
txtConsloe.Invoke(new Action(() => { txtConsloe.Text += info; }));
}
//輸出資訊
private string getInfo(string info)
{
return info + " Thread :" + Thread.CurrentThread.ManagedThreadId + " " + DateTime.Now.ToString("G") + Environment.NewLine;
}
//獲取按鈕文字
private string bt(object sender)
{
return ((Button)sender).Text;
}
#endregion
#region 非同步方法
private async Task MethodAsync()
{
showConsole("MethodAsync Start");
string Result = await LongTimeMethod();
showConsole(Result);
showConsole("MethodAsync End");
}
private async Task<string> MethodAsyncDeadlock()
{
showConsole("MethodAsyncDeadlock Start");
string Result = await LongTimeMethod();
showConsole("MethodAsyncDeadlock End");
return Result;
}
private Task<string> LongTimeMethod()
{
var task = Task.Run(() =>
{
showConsoleDelegate("LongTimeMethod Start ");
Thread.Sleep(3000);
showConsoleDelegate("LongTimeMethod End ");
return "LongTimeMethod Over";
});
return task;
}
#endregion
}