您的位置:首页 > 大数据 > 人工智能

async 和 await 关键字

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

var result = await expression;
statement(s);

这种写法相当于:

var awaiter = expression.GetAwaiter();
awaiter.OnCompleted (() =>
{
var result = awaiter.GetResult();
statement(s);
);

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

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

这是比较常见的写法。

要异步执行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对象比较麻烦,一个对象必须满足下列条件才行:

必须有一个 GetAwaiter()方法,扩展方法或者实例方法都可以

GetAwaiter() 方法返回值必须是awaiter对象。一个对象要成为awaiter对象必须满足下列条件:

该对象实现接口 INotifyCompletion 或者ICriticalNotifyCompletion

必须有 IsCompleted属性

必须有 GetResult()方法,可以返回void或者其他返回值。

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

public interface IAwaitable<out TResult>
{
IAwaiter<TResult> GetAwaiter();
}
public interface IAwaiter<out TResult> : INotifyCompletion // or ICriticalNotifyCompletion
{
bool IsCompleted { get; }
TResult GetResult();
}

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

public static class FuncExtensions
{
public static IAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function)
{
return new FuncAwaiter<TResult>(function);
}
}
public interface IAwaitable<out TResult> { IAwaiter<TResult> GetAwaiter(); } public interface IAwaiter<out TResult> : INotifyCompletion // or ICriticalNotifyCompletion { bool IsCompleted { get; } TResult GetResult(); }internal struct FuncAwaitable<TResult> : IAwaitable<TResult>
{
private readonly Func<TResult> function;
public FuncAwaitable(Func<TResult> function)
{
this.function = function;
}
public IAwaiter<TResult> GetAwaiter()
{
return new FuncAwaiter<TResult>(this.function);
}
}
public struct FuncAwaiter<TResult> : IAwaiter<TResult>
{
private readonly Task<TResult> task;
public FuncAwaiter(Func<TResult> function)
{
this.task = new Task<TResult>(function);
this.task.Start();
}
bool IAwaiter<TResult>.IsCompleted
{
get
{
return this.task.IsCompleted;
}
}
TResult IAwaiter<TResult>.GetResult()
{
return this.task.Result;
}
void INotifyCompletion.OnCompleted(Action continuation)
{
new Task(continuation).Start();
}
}

在main中可以如下写:

static void Main(string[] args)
{
Func(() => { Console.WriteLine("await..");return 0;});
Thread.Sleep(5000);//等待异步执行完成
}
static async void Func(Func<int> f)
{
int result = await new Func<int>(f);
Console.WriteLine(result);
}

其中:

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

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

static void Main(string[] args)
{
Func(() =>
{
return Task<int>.Run<int>(() => { return Enumerable.Range(1,100).Sum(); });
});
Thread.Sleep(5000);//等待异步执行完成
}
static async void Func(Func<Task<int>> f)
{
int result = await f();
Console.WriteLine(result);
}
---------------------------------

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

http://weblogs.asp.net/dixin/archive/2012/11/08/understanding-c-async-await-2-awaitable-awaiter-pattern.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: