C#沉澱-非同步程式設計 二
針對於await表示式的異常處理
using System;
using System.Net;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Threading;
namespace CodeForAsync
{
class Program
{
//定義一個非同步方法
static async Task BadAsync()
{
try
{
//故意丟擲一個異常
await Task.Run(() => { throw new Exception(); });
}
catch (Exception ex)
{
//捕獲異常
Console.WriteLine("捕獲一個非同步"+ex.ToString());
}
}
static void Main(string[] args)
{
Task t = BadAsync();
t.Wait();
Console.WriteLine("檢視非同步方法的狀態:"+t.Status);
Console.WriteLine("檢視是否有未經處理的異常:"+t.IsFaulted);
Console.ReadKey();
}
}
}
輸出:
捕獲一個非同步System.Exception: 引發型別為“System.Exception”的異常。 在 CodeForAsync.Program.<BadAsync>b__0() 位置 f:\我的文件\文件\C# 沉澱\CodeForAsync\CodeForAsync\Program.cs:行號 18 在 System.Threading.Tasks.Task`1.InnerInvoke() 在 System.Threading.Tasks.Task.Execute() --- 引發異常的上一位置中堆疊跟蹤的末尾 --- 在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 在 System.Runtime.CompilerServices.TaskAwaiter.GetResult() 在 CodeForAsync.Program.<BadAsync>d__2.MoveNext() 位置 f:\我的文件\文件\C# 沉澱\CodeForAsync\CodeForAsync\Program.cs:行號 18 檢視非同步方法的狀態:RanToCompletion 檢視是否有未經處理的異常:False
從輸出結果來看,Task的狀態為RanToCompletion,說明即使捕獲到異常也不會令Task終止或取消;IsFaulted的狀態為False,說明沒有未處理的異常
在呼叫方法中同步的等待任務
Task提供了一個例項方法Wait,可以在Task上呼叫該方法,同步的等待任務的完成
示例:
using System;
using System.Net;
using System.Diagnostics;
using System.Threading.Tasks;
namespace CodeForAsync
{
static class MyDownloadString
{
public static void DoRun()
{
Task<int> t1 = CountCharactersAsync(1, "http://baidu.com");
//等待任務結束
t1.Wait();
Console.WriteLine("任務已經結束,值:" + t1.Result);
}
//下載網站資源
private static async Task<int> CountCharactersAsync(int id, string uristring)
{
string result = await (new WebClient()).DownloadStringTaskAsync(new Uri(uristring));
return result.Length;
}
}
class Program
{
static void Main(string[] args)
{
MyDownloadString.DoRun();
Console.ReadKey();
}
}
}
這裡的Wait
用在了單一的Task
物件上,也可以用於一組Task
上,但是需要用到Task上的兩個靜態方法:
示例:WaitAll
和WaitAny
using System;
using System.Net;
using System.Diagnostics;
using System.Threading.Tasks;
namespace CodeForAsync
{
class MyDownloadString
{
Stopwatch sw = new Stopwatch();
public void DoRun()
{
//下載百度資源
Task<int> t1 = CountCharactersAsync(1, "http://baidu.com");
//下載搜狗資源
Task<int> t2 = CountCharactersAsync(2, "https://pinyin.sogou.com/");
///WaitAll會等待所包含的所有的Task都完成後才會往下執行
///WaitAny會等待所包含的所有的Taks中只要有一個完成即會向下執行
Task<int>[] tasks = new Task<int>[] { t1, t2 };
//Task.WaitAll(tasks);
Task.WaitAny(tasks);
///下面的寫法也是允許的
///這兩個靜態方法接收的是Taks型別的陣列
///下面實現了隱式的轉換
//Task.WaitAll(t1, t2);
//Task.WaitAny(t1, t2);
Console.WriteLine("任務一是否完成:" + (t1.IsCompleted ? "完成" : "未完成").ToString());
Console.WriteLine("任務二是否完成:" + (t2.IsCompleted ? "完成" : "未完成").ToString());
}
//下載網站資源
private async Task<int> CountCharactersAsync(int id, string uristring)
{
WebClient wc = new WebClient();
string result = await wc.DownloadStringTaskAsync(new Uri(uristring));
return result.Length;
}
}
class Program
{
static void Main(string[] args)
{
MyDownloadString ds = new MyDownloadString();
ds.DoRun();
Console.ReadKey();
}
}
}
輸出:
任務一是否完成:完成
任務二是否完成:未完成
WaitAll與WaitAny有許多過載,這裡簡單介紹兩個:
//等待所有任務完成,或CancellationToken發出了取消訊號
public static void WaitAll(Task[] tasks, CancellationToken cancellationToken);
//等待所有任務完成,如果在等待時間內仍未完成,則繼續往下執行
public static bool WaitAll(Task[] tasks, int millisecondsTimeout);
public static bool WaitAll(Task[] tasks, TimeSpan timeout);
//等待所有任務完成,如果發生超時或者有取消動作,則繼續往下執行不再等待
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
/////////////////////////////////////////
//等待一個任務完成,或CancellationToken發出了取消訊號
public static void WaitAny(Task[] tasks, CancellationToken cancellationToken);
//等待一個任務完成,如果在等待時間內仍未完成,則繼續往下執行
public static bool WaitAny(Task[] tasks, int millisecondsTimeout);
public static bool WaitAny(Task[] tasks, TimeSpan timeout);
//等待一個任務完成,如果發生超時或者有取消動作,則繼續往下執行不再等待
public static bool WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
在非同步方法中非同步的等待任務
await
會等待一個或所有的任務完成,
可以通過Task.WhenAll
與Task.WhenAny
方法來實現。這兩個方法稱為組合子
示例:
using System;
using System.Net;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace CodeForAsync
{
class MyDownloadString
{
public void DoRun()
{
//下載百度資源
Task<int> t = CountCharactersAsync("http://baidu.com", "https://pinyin.sogou.com");
Console.WriteLine("任務t是否已經完成:{0}", t.IsCompleted ? "完成" : "未完成");
Console.WriteLine("輸出值:{0}", t.Result);
}
//下載網站資源
private async Task<int> CountCharactersAsync(string uristring1, string uristring2)
{
WebClient wc1 = new WebClient();
WebClient wc2 = new WebClient();
Task<string> t1 = wc1.DownloadStringTaskAsync(new Uri(uristring1));
Task<string> t2 = wc2.DownloadStringTaskAsync(new Uri(uristring2));
List<Task<string>> tasks = new List<Task<string>>();
tasks.Add(t1);
tasks.Add(t2);
//await Task.WhenAll(tasks);
await Task.WhenAny(tasks);
Console.WriteLine("任務一是否完成:{0}", t1.IsCompleted ? "完成" : "未完成");
Console.WriteLine("任務二是否完成:{0}", t2.IsCompleted ? "完成" : "未完成");
return t1.IsCompleted ? t1.Result.Length : t2.Result.Length;
}
}
class Program
{
static void Main(string[] args)
{
MyDownloadString ds = new MyDownloadString();
ds.DoRun();
Console.ReadKey();
}
}
}
輸出:
任務t是否已經完成:未完成
任務一是否完成:完成
任務二是否完成:未完成
輸出值:81
await 會等待一個Task任務的完成,如果await 呼叫Task.WhenAll,則會非同步的等待所有方法都被呼叫完;而await呼叫Task.WhenAny時,則會非同步的等待其中一個任務完成;同樣,當代碼遇到await時,會返回到呼叫方法上去
上例中使用了Task.WhenAny(tasks);
,所以,當任務一完成而任務二未完成時,程式碼便向下執行了,不再等待,如果換成Task.WhenAll(tasks);
,則程式碼會等待兩個任務完成後才會向下執行
WaitAll/WaitAny/WhenAll/WhenAny之間的區別
最好理解的是WaitAll
和WaitAny
,它們都是發生在呼叫層的等待,阻塞當前執行緒,等待所有的非同步方法有所返回以後才會繼續當前的執行緒
而WhenAll
與WhenAny
也是等待,不過不是在呼叫層的執行緒中等待,而是在非同步方法裡等待,所以這裡說它是“非同步的等待”,它阻塞的是非同步方法的內部過程,而呼叫方法因為遇到await會反加到呼叫層,所以呼叫層的執行緒不會被阻塞
也就是說它們的效果是一樣的,但所在的包裝層級不同
Task.Delay方法
Task.Delay
方法會使任務暫停一段時間,我們知道Thread.Sleep
也可以將程式暫停一段時間,不同的是,Task.Delay
就在非同步方法內部使用,不會導致呼叫層執行緒阻塞,而Thread.Sleep
即使是在非同步方法內部他用,也可能使用呼叫層被阻塞
示例:
using System;
using System.Net;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
namespace CodeForAsync
{
class MyClass
{
public async void Hello()
{
Console.WriteLine("節點1");
Thread.Sleep(1000);
// await Task.Delay(1000);
await Task.Run(() =>
{
Console.WriteLine("節點2");
});
}
}
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.Hello();
Thread.Sleep(100);
Console.WriteLine("節點3");
Console.ReadKey();
}
}
}
輸出:
節點1
節點2
節點3
Thread.Sleep(1000);
會將當前執行緒與呼叫層的執行緒都阻塞,如果使用await Task.Delay(1000);
的話,輸出如下:
節點1
節點3
節點2