catch和finally块
虽然在try块中使用await是完全允许的,但是他不允许在catch和finally块中使用。通常在catch和finall块中,异常依然在堆栈中未解决的状态,并且之后将会被抛出。如果await在这个时刻前使用,栈将会有所不同,并且抛出异常的行为将会变得难以定义。
请记住替代在catch块中使用block的方法是在其后面,通过返回一个布尔值来记录操作是否抛出一个异常。示例如下:
try
{
page = await webClient.DownloadStringTaskAsync("http://oreilly.com");
}
catch (WebException)
{
page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");
}
你可以以如下方式替代:
bool failed = false;
try
{
page = await webClient.DownloadStringTaskAsync("http://oreilly.com");
}
catch (WebException)
{
failed = true;
}
if (failed)
{
page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");
}
lock块
lock是一种帮助编程人员防止其它线程和当前线程访问相同对象的方式。因为异步代码通常会释放开始执行异步的线程,并且会被回调并且发生回调在一个不确定的时间量之后,即被释放掉后和开始的线程不同(译者:即使相同的线程,它也是释放掉之后的了),所以在await上加锁没有任何意义。
在一些情况下,保护你的对象不被并发访问是很重要的,但是在没有其他线程在await期间来访问你的对象,使用锁是没有必要的。在这些情况下,你的操作是有些冗余的,显式地锁定了两次,如下:
lock (sync)
{
// Prepare for async operation
}
int myNum = await AlexsMethodAsync();
lock (sync)
{
// Use result of async operation
}
另外,你可以使用一个类库来进行处理并发控制,比如NAct,我们将会在第十章介绍
如果你不够幸运,你可能需要在执行异步操作时保持某种锁。这时,你就需要苦思冥想并小心谨慎,因为通常锁住异步调用资源,而不造成争用和死锁是非常困难的。也许遇到这种情况想其他办法或者重构你的程序是最好的选择。
Linq Query表达式
C#有一种语法帮助我们更加容易的去通过书写querys来达到过滤,排序,分组等目的。这些query可以被执行在.NET平台上或者转换成数据库操作甚至其他数据源操作。
IEnumerable<int> transformed = from x in alexsInts
where x != 9
select x + 2;
C#是在大多数位置是不允许在Query表达式中使用await关键字的。是因为这些位置会被编译成lambda表达式,正因为如此,该lambda表达式需要标记为async关键字。只是这样含蓄的lambda表达式不存在,即使如果真的这样做也会让人confuse。
我们还是有办法,你可以写当量的表达式,通过使用Linq内部带的拓展方法。然后lambda表达式变得明了可读,继而你也就可以标记他们为async,从而使用await了。(译者:请对照上下代码来阅读)
IEnumerable<Task<int>> tasks = alexsInts
.Where(x => x != 9)
.Select(async x => await DoSomthingAsync(x) + await DoSomthingElseAsync(x));
IEnumerable<int> transformed = await Task.WhenAll(tasks);
为了收集结果,我使用了Task.WhenAll,这是为Task集合所工作的工具,我将会在第七章介绍细节。
不安全的代码
代码被标记为unsafe的不能包含await,非安全的代码应该做到非常罕见并且应该保持方法独用和不需要异步。反正在编译器对await做转换的时候也会跳出unsafe代码。(译者:我觉得其实这里不用太在意啦,反正没写过unsafe关键字的代码)