针对于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.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.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 t1 = CountCharactersAsync(1, "http://baidu.com");
//等待任务结束
t1.Wait();
Console.WriteLine("任务已经结束,值:" + t1.Result);
}
//下载网站资源
private static async Task 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 t1 = CountCharactersAsync(1, "http://baidu.com");
//下载搜狗资源
Task t2 = CountCharactersAsync(2, "https://pinyin.sogou.com/");
///WaitAll会等待所包含的所有的Task都完成后才会往下执行
///WaitAny会等待所包含的所有的Taks中只要有一个完成即会向下执行
Task[] tasks = new Task[] { 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 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 t = CountCharactersAsync("http://baidu.com", "https://pinyin.sogou.com");
Console.WriteLine("任务t是否已经完成:{0}", t.IsCompleted ? "完成" : "未完成");
Console.WriteLine("输出值:{0}", t.Result);
}
//下载网站资源
private async Task CountCharactersAsync(string uristring1, string uristring2)
{
WebClient wc1 = new WebClient();
WebClient wc2 = new WebClient();
Task t1 = wc1.DownloadStringTaskAsync(new Uri(uristring1));
Task t2 = wc2.DownloadStringTaskAsync(new Uri(uristring2));
List> tasks = new List>();
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
Task.Delay(1000)际上只是创建了一个消耗1000毫秒的任务罢了,因此他需要额外的消耗资源