併發系列64章(非同步程式設計二)第三章
前言
是在第二章基礎上續寫的,主要是完結第二章例子部分。
請看下面幾個例子,感受一下。
報告進度
不管我們完任何app,每次更新的時候都能看到進度條。
而我們知道ui介面更新,一般來說是和更新程式非同步的,但是更新程式又要通知ui進度。
程式碼:
public class Program { static double percentComplete = 0; static void Main(string[] args) { doit(); Console.ReadKey(); } public static void DoLoop() { Task.Run(() => { for (var i = 1; i <= 100000; i++) { setPercent(i/1000); Console.WriteLine(i); } setPercent(101); }); } public async static void doit() { var pregress = CallProcessTask(); var task=MyMethodAsync(pregress); DoLoop(); await task; } public static void setPercent(int percent) { percentComplete = percent; } public static Progress<double> CallProcessTask() { var progress = new Progress<double>(); progress.ProgressChanged += (sender, args) => { //在這裡做一些進度變化,顯示在ui上。 Console.WriteLine("process:"+args); }; return progress; } public static Task MyMethodAsync(IProgress<double> progress=null) { var task= Task.Run(() => { while (percentComplete <= 100) { if (progress != null) { Console.WriteLine("檢視進度:" + percentComplete); progress.Report(percentComplete); } } }); return task; } }
上面我想做的事,一個非同步的事件——DoLoop。
可能初學者,很喜歡用async 和 await ,可能會認為沒有async 和 await的就不是非同步事件。
async和 await的存在的作用就在於等待當前非同步事件完成,如果沒有這個需求,那麼你為什麼要寫呢?
MyMethodAsync 是另一個非同步事件,裡面做的事監聽當前進度。
CallProcessTask是構造一個progress,並寫下委託,監聽percentComplete的改變。
等待一組任務完成
static void Main(string[] args) { int[] result = DoAll().Result; Console.ReadKey(); } public static async Task<int[]> DoAll() { Task<int> task1 = Task.FromResult(1); Task<int> task2 = Task.FromResult(2); Task<int> task3 = Task.FromResult(3); int[] result=await Task.WhenAll<int>(task1, task2, task3); return result; }
WhenAll 是一個新的task,管理一組Task。監聽一組task進度,當全部的task結束,這個task也結束。
使用await的時候,但是當有一個whenall 管理的task果然有多個task失敗,那麼只會報一個錯誤。
而使用whenall,我們的需求是要全部成功,要是有一個不成功那麼也應該是失敗的。所以我們只要截獲一個錯誤是正確的,系統也是這麼做的。
下面是截獲程式碼:
public static async Task<int[]> DoAll() { Task<int> task1 = Task.FromResult(1); Task<int> task2 = Task.FromResult(2); Task<int> task3 = Task.FromResult(3); Task<int[]> Alltasks = Task.WhenAll<int>(task1, task2, task3); try { var result=await Alltasks; return result; } catch { AggregateException allException = Alltasks.Exception; // 處理錯誤 } return null; }
等待任意一個任務完成
為啥會有這種需求?可能你覺得我們每次訪問的時候都是對一條url,比如說我們訪問百度,那麼我們不就是訪問www.baidu.com。
但是呢,有幾個運營服務商,做的相同的業務,都是免費的,但是公司保險起見呢,一起呼叫,為了確保在不同地域的請求穩定。
static void Main(string[] args)
{
var result = DoAny('www.baidu.com','www.baidu.com').Result;
Console.ReadKey();
}
public static async Task<byte[]> DoAny(string urlA,string urlB)
{
var httpClient = new HttpClient();
Task<Byte[]> DownLoadTaskA = httpClient.GetByteArrayAsync(urlA);
Task<Byte[]> DownLoadTaskB = httpClient.GetByteArrayAsync(urlB);
var completedTask =await Task.WhenAny<byte[]>(DownLoadTaskA, DownLoadTaskB);
return await completedTask;
}
這裡值得關注的是為什麼有兩個await:
var completedTask =await Task.WhenAny<byte[]>(DownLoadTaskA, DownLoadTaskB);
return await completedTask;
因為建立WhenAny 的Task是非同步的,而建立 whenAll 的Task 是同步的。
值得注意的是whenAny 當一個任務完成時,那麼會返回這個Task。其他任務依然還是在執行,需要考慮的是中斷一個task更好,還是直接讓他執行完更好。這是需要考慮的地方。
任務完成後處理
比如說有3個任務,我希望只要完成任意一個任務完成後馬上接下來完成它的後續任務。
如果我這樣寫的話:
static void Main(string[] args)
{
ProcessTasksAsyns();
Console.ReadKey();
}
public static async Task ProcessTasksAsyns()
{
Task<int> TaskA = DelayAndReturnAsync(2);
Task<int> TaskB = DelayAndReturnAsync(3);
Task<int> TaskC = DelayAndReturnAsync(1);
Task<int>[] tasks = new[] { TaskA, TaskB, TaskC };
foreach (var task in tasks)
{
var result = await task;
Console.WriteLine("後續執行:"+result);
}
}
static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
Console.WriteLine(val);
return val;
}
但是得到的卻是這樣的結果。
這個結果顯示Task 完成的順序是 1 ,2,3秒。
但是執行後續的順序是await的順序,也就是2,3,1.
解決方案是將3個任務,分別放在另外3個任務中執行。
static void Main(string[] args)
{
ProcessTasksAsyns();
Console.ReadKey();
}
public static async Task ProcessTasksAsyns()
{
Task<int> TaskA = DelayAndReturnAsync(2);
Task<int> TaskB = DelayAndReturnAsync(3);
Task<int> TaskC = DelayAndReturnAsync(1);
Task<int>[] tasks = new[] { TaskA, TaskB, TaskC };
var processingTasks = tasks.Select(async t =>
{
var result = await t;
Trace.WriteLine(result);
});
await Task.WhenAll(processingTasks);
}
static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
Console.WriteLine(val);
return val;
}
當然後面會介紹到其他方法,但是這個解釋了,如果去實現這種需求。
未完
還有幾個例子,下章結束例子