async 和 await 关键字

C# 5.0中引入了async 和 await。这两个关键字可以让你更方便的按照同步的方式写出异步代码。也就是说使你更方便的异步编程。常规的写法格式如下:

 
 
  1. var result = await expression
  2. statement(s); 

这种写法相当于: 

 
 
  1. var awaiter = expression.GetAwaiter(); 
  2. awaiter.OnCompleted (() => 
  3. var result = awaiter.GetResult(); 
  4. statement(s); 
  5. ); 

这里的expression通常是Task或者Task<TResult>,但是事实上可以自己定义一些可以使用await的对象。但是要满足一定的条件。先看一个例子。 

 
 
  1. static void Main(string[] args) 
  2.        { 
  3.            DisplayPrimesCount(); 
  4.            Thread.Sleep(5000);//等待异步执行完成 
  5.        } 
  6.        static Task<int> GetPrimesCountAsync(int start, int count) 
  7.        { 
  8.            return Task.Run(() => 
  9.            ParallelEnumerable.Range(start, count).Count(n => 
  10.            Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0))); 
  11.        } 
  12.        static async void DisplayPrimesCount() 
  13.        { 
  14.            int result = await GetPrimesCountAsync(2, 1000000);//此处会阻塞 
  15.            Console.WriteLine(result); 
  16.        } 

这是比较常见的写法。

要异步执行GetPrimesCountAsync方法,首先GetPrimesCountAsync返回的必须是"可等待"对象。上述代码中GetPrimesCountAsync返回的是Task<int>类型。然后,使用await的方法必须要标注async关键字。并且可以看到await GetPrimesCountAsync(2, 1000000);这个语句返回的是int,而不是Task<int>。

上述代码用自然语言描述就是如下:

当调用DisplayPrimesCount,DisplayPrimesCount会运行一个新的Task,这个task会计算素数的个数。完成后,会将计算所得的值返回,并将这个返回值放到Task对象中,并且返回给调用者。调用者获得这个Task值后,取出Task的result值。

当程序逻辑遇到await GetPrimesCountAsync方法,线程就会被挂起,直到异步运行完成,得到result值后,再会继续运行下去。

本质上说await和async的出现也只是一颗语法糖,但是这颗语法糖可以使得异步编程更优雅,直接摒弃了原先EAP和APM这种到处BeginXXX,EndXXX的丑陋模式,提高了生产力。

可以使用await的方法,返回值必须是awaitable对象,自定义awaitable对象比较麻烦,一个对象必须满足下列条件才行:

  1. 必须有一个 GetAwaiter()方法,扩展方法或者实例方法都可以
  2. GetAwaiter() 方法返回值必须是awaiter对象。一个对象要成为awaiter对象必须满足下列条件:
    • 该对象实现接口 INotifyCompletion 或者ICriticalNotifyCompletion
    • 必须有 IsCompleted属性
    • 必须有 GetResult()方法,可以返回void或者其他返回值。

由于微软并未给出满足上述条件的接口,因此可以自己实现这样的接口。 

 
 
  1. public interface IAwaitable<out TResult> 
  2.     { 
  3.         IAwaiter<TResult> GetAwaiter(); 
  4.     } 
  5.     public interface IAwaiter<out TResult> : INotifyCompletion // or ICriticalNotifyCompletion 
  6.     { 
  7.         bool IsCompleted { get; } 
  8.         TResult GetResult(); 
  9.     } 

由于对于拉姆达表达式不可以直接使用await,因此可以通过编程,技巧性的实现这一功能。比如对某一个Func委托实现扩展方法,注意: 扩展方法必须在顶级静态类中定义。 

 
 
  1. public static class FuncExtensions 
  2.     { 
  3.         public static IAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function) 
  4.         { 
  5.             return new FuncAwaiter<TResult>(function); 
  6.         } 
  7.     } 
  8.     public interface IAwaitable<out TResult> 
  9.     { 
  10.         IAwaiter<TResult> GetAwaiter(); 
  11.     } 
  12.     public interface IAwaiter<out TResult> : INotifyCompletion // or ICriticalNotifyCompletion 
  13.     { 
  14.         bool IsCompleted { get; } 
  15.         TResult GetResult(); 
  16.     } 
  17.     internal struct FuncAwaitable<TResult> : IAwaitable<TResult> 
  18.     { 
  19.         private readonly Func<TResult> function; 
  20.         public FuncAwaitable(Func<TResult> function) 
  21.         { 
  22.             this.function = function; 
  23.         } 
  24.         public IAwaiter<TResult> GetAwaiter() 
  25.         { 
  26.             return new FuncAwaiter<TResult>(this.function); 
  27.         } 
  28.     } 
  29.     public struct FuncAwaiter<TResult> : IAwaiter<TResult> 
  30.     { 
  31.         private readonly Task<TResult> task; 
  32.         public FuncAwaiter(Func<TResult> function) 
  33.         { 
  34.             this.task = new Task<TResult>(function); 
  35.             this.task.Start(); 
  36.         } 
  37.         bool IAwaiter<TResult>.IsCompleted 
  38.         { 
  39.             get 
  40.             { 
  41.                 return this.task.IsCompleted; 
  42.             } 
  43.         } 
  44.         TResult IAwaiter<TResult>.GetResult() 
  45.         { 
  46.             return this.task.Result; 
  47.         } 
  48.         void INotifyCompletion.OnCompleted(Action continuation) 
  49.         { 
  50.             new Task(continuation).Start(); 
  51.         } 
  52.     } 
  53.      
  54. 在main中可以如下写: 
  55.  
  56.   static void Main(string[] args) 
  57.         { 
  58.             Func(() => { Console.WriteLine("await..");return 0;}); 
  59.             Thread.Sleep(5000);//等待异步执行完成 
  60.         } 
  61.         static async void Func(Func<int> f) 
  62.         { 
  63.             int result = await new Func<int>(f); 
  64.             Console.WriteLine(result); 
  65.         } 

其中:

Func方法可以异步执行了,因为Func<int>已经实现扩展方法GetAwaiter,并且返回值类型是自己定义的IAwaitable类型。

当然,更加简单的方法是,采用微软提供的Task对象,让拉姆达表达式返回Task类型就可以了。 

 
 
  1. static void Main(string[] args) 
  2.         { 
  3.             Func(() => 
  4.             { 
  5.                 return Task<int>.Run<int>(() => { return Enumerable.Range(1,100).Sum(); }); 
  6.             }); 
  7.             Thread.Sleep(5000);//等待异步执行完成 
  8.         } 
  9.         static async void Func(Func<Task<int>> f) 
  10.         { 
  11.             int result = await f(); 
  12.             Console.WriteLine(result); 
  13.         } 
---------------------------------

参考资料:《C# 5.0 IN A NUTSHELL》

http://weblogs.asp.net/dixin/archive/2012/11/08/understanding-c-async-await-2-awaitable-awaiter-pattern.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值