>>回傳《C# 并發編程》
- 1. 概述
- 2. 報告進度
- 3. 等待一組任務完成
- 4. 例外處理
- 5. 等待任意一個任務完成
- 6. 避免背景關系延續
- 7. async void
1. 概述
前面的文章介紹了標識了 async 和 await 的代碼,是怎么被執行緒執行的,
>>同步背景關系-7.5 異步編程(Async)
下面介紹一些類別庫和常用的API,
2. 報告進度
使用 IProgress<T> 和 Progress<T> 型別
- 構造
Progress<T>實體時捕獲當前 同步背景關系 實體; Progress<T>實體的ProgressChanged事件被呼叫時使用上面捕獲的同步背景關系;- 如果在執行建構式的執行緒沒有同步背景關系時(隱含使用的Default同步背景關系),則將在 ThreadPool 中呼叫事件
static async Task DoProgressAsync(int count, IProgress<int> progress = null)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(200);
if (progress != null)
progress.Report(i + 1);
}
}
static async Task CallProgressAsync()
{
int count = 5;
var progress = new Progress<int>();
progress.ProgressChanged += (sender, args) =>
{
System.Console.WriteLine($"{args}/{count}");
};
await DoProgressAsync(count, progress);
}
輸出為:
1/5
2/5
3/5
4/5
5/5
3. 等待一組任務完成
Task.WhenAll 方法有以 IEnumerable 型別作為引數的多載,但建議大家不要使用,
- 呼叫
ToList或ToArray方法后,序列中沒有啟動的任務就開始了
static async Task DownloadAllAsync()
{
Stopwatch sw = new Stopwatch();
sw.Start();
IEnumerable<string> urls = new string[]{
"https://www.baidu.com/",
"https://cn.bing.com/"
};
var httpClient = new HttpClient();
// 定義每一個 url 的使用方法,
var downloads = urls.Select(url =>
{
Console.WriteLine($"{url}:start");
var res = httpClient.GetStringAsync(url);
res.ContinueWith(t => Console.WriteLine($"{url}:{sw.ElapsedMilliseconds}ms"));
return res;
});
// 注意,到這里,序列還沒有求值,所以所有任務都還沒真正啟動,
// 下面,所有的 URL 下載同步開始,
Task<string>[] downloadTasks = downloads.ToArray();
// 到這里,所有的任務已經開始執行了,
Console.WriteLine($"await Task.WhenAll");
// 用異步方式等待所有下載完成,
string[] htmlPages = await Task.WhenAll(downloadTasks);
Console.WriteLine($"jobs done.");
}
輸出為:
https://www.baidu.com/:start
https://cn.bing.com/:start
await Task.WhenAll
https://www.baidu.com/:270ms
jobs done.
; 由于回傳的是請求的 Task 不是 ContinueWith 的列印 Task
https://cn.bing.com/:1089ms
4. 例外處理
- 如果有一個任務拋出例外,則
Task.WhenAll會出錯,并把這個例外放在回傳的Task中 - 如果多個任務拋出例外,則這些例外都會放在回傳的
Task中 - 如果這個
Task在被await呼叫,就只會拋出該異步方法的一個例外 - 如果要得到每個例外,可以檢查
Task.WhenAll回傳的Task的Exception屬性:
示例:
static async Task ThrowNotImplementedExceptionAsync()
{
await Task.Delay(10);
throw new NotImplementedException();
}
static async Task<int> ThrowInvalidOperationExceptionAsync()
{
TaskCompletionSource<int> completionSource = new TaskCompletionSource<int>();
completionSource.TrySetException(new InvalidOperationException());
return await completionSource.Task;
}
static async Task ObserveOneExceptionAsync()
{
System.Console.WriteLine("OneException");
var task1 = ThrowNotImplementedExceptionAsync();
var task2 = ThrowInvalidOperationExceptionAsync();
try
{
await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
// ex 要么是 NotImplementedException,要么是 InvalidOperationException
System.Console.WriteLine(ex.GetType().Name);
}
}
static async Task ObserveAllExceptionsAsync()
{
System.Console.WriteLine("AllExceptions");
var task1 = ThrowNotImplementedExceptionAsync();
var task2 = ThrowInvalidOperationExceptionAsync();
Task allTasks = Task.WhenAll(task1, task2);
try
{
await allTasks;
}
catch
{
AggregateException allExceptions = allTasks.Exception;
allExceptions.Handle(ex =>
{
System.Console.WriteLine(ex.GetType().Name);
return true;
});
}
}
輸出為:
OneException
NotImplementedException
AllExceptions
NotImplementedException
InvalidOperationException
5. 等待任意一個任務完成
// 回傳第一個回應的 URL 的資料長度,
private static async Task<int> FirstRespondingUrlAsync()
{
string urlA = "https://www.baidu.com/";
string urlB = "https://cn.bing.com/";
var httpClient = new HttpClient();
// 并發地開始兩個下載任務,
Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
// 等待任意一個任務完成,
Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
// 回傳從 URL 得到的資料的長度,
byte[] data = https://www.cnblogs.com/BigBrotherStone/p/await completedTask;
Console.WriteLine($"Finish: {(completedTask == downloadTaskA ? nameof(downloadTaskA) : nameof(downloadTaskA))}");
Console.WriteLine($"downloadTaskA: {downloadTaskA.Status}");
Console.WriteLine($"downloadTaskB: {downloadTaskB.Status}");
return data.Length;
}
輸出為:
Finish: downloadTaskA
downloadTaskA: RanToCompletion
downloadTaskB: WaitingForActivation
如果這個任務完成時有例外,這個例外也不會傳遞給 Task.WhenAny 回傳的 Task 物件,因此,通常需要在 Task 物件完成后繼續使用 await,
第一個任務完成后,考慮是否要取消剩下的任務,如果其他任務沒有被取消,也沒有被繼續 await,那它們就處于被遺棄的狀態,被遺棄的任務會繼續運行直到完成,它們的結果會被忽略,拋出的任何例外也會被忽略,
//每個任務需要等到Trace.WriteLine執行完才能執行下一個
static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
// 當前,此方法輸出“2”,“3”,“1”, // 我們希望它先輸出先完成的,期望 輸出“1”,“2”,“3”,
static async Task ProcessTasksAsync1()
{
// 創建任務佇列,
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
// 按順序 await 每個任務,
foreach (var task in tasks)
{
var result = await task;
Console.WriteLine(result);
}
}
//不等Trace.WriteLine切任務并行的解決方案
// 現在,這個方法輸出“1”,“2”,“3”,
static async Task ProcessTasksAsync2()
{
// 創建任務佇列,
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
var processingTasks = tasks.Select(async t =>
{
var result = await t;
Console.WriteLine(result);
}).ToArray();
// 等待全部處理程序的完成,
await Task.WhenAll(processingTasks);
}
static async Task ProcessTasksAsyncExe()
{
Stopwatch sw = Stopwatch.StartNew();
System.Console.WriteLine("ProcessTasksAsync1");
await ProcessTasksAsync1();
System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
sw.Restart();
System.Console.WriteLine();
System.Console.WriteLine("ProcessTasksAsync2");
await ProcessTasksAsync2();
System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
}
輸出為:
ProcessTasksAsync1
2
3
1
3007ms
ProcessTasksAsync2
1
2
3
3004ms
6. 避免背景關系延續
默認情況下,一個 async 方法在被 await 呼叫后恢復運行時,會在原來的背景關系中運行, 如果是 UI背景關系 ,并且有大量的 async 方法在 UI背景關系 中恢復,就會引起性能上的問題,
ConfigureAwait(true)延續背景關系(執行完異步await,回到同步背景關系)ConfigureAwait(false)不延續背景關系(執行完異步await,由于沒有記錄之前的同步背景關系,后續代碼在 Default背景關系 中運行)
7. async void
處理 async void 方法的例外有一個辦法:
- 一個例外從
async void方法中傳遞出來時,會在其同步背景關系中引發出來 - 當
async void方法啟動時,同步背景關系 就處于激活狀態- 如果系統運行環境有特定的 同步背景關系(如:UI同步背景關系,ASP.Net同步背景關系),通常就可以在全域范圍內處理這些頂層的例外
- PF 有
Application.DispatcherUnhandledException, - WinRT 有
Application.UnhandledException - ASP.NET 有
Application_Error
- PF 有
- 如果系統運行環境有特定的 同步背景關系(如:UI同步背景關系,ASP.Net同步背景關系),通常就可以在全域范圍內處理這些頂層的例外
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/77413.html
標籤:C#
