异步编程async/await详解
1.关键字async
当函数使用async标记后,返回值必须为void,Task,Task<T>,当返回值为Task<T>时,函数内部只需要返回T类型,编译器会自动包装成Task<T>类型,如下两个函数执行结果一致:
public Task<int> F1()
{
return Task.FromResult(5);
}
public async Task<int> F2()
{
return 5;
}
2.关键字await
await关键字必须在具有async标记的函数内使用,使用await关键字后编译器会使用状态机进行编译,await关键字不仅仅只可以对Tsak对象使用,我们也可以自己实现await的对象。当函数使用了一个await关键字后,会被分割成两部分,编译的时候会当作函数的两个状态。函数的两部分会分别是await前面的一部分,和await后面的一部分,例如:
这两部分运行的位置不一样,第一部分和正常函数一样调用函数的时候直接执行,第二部分的执行取决于我们await的对象,执行的位置也在await对象内部。下面是一个await对象的实现,展示了各个部分的执行过程。
public class A
{
private B b = new B();
public B GetAwaiter()
{
Console.WriteLine("A.GetAwaiter");
return b;
}
}
public class B : INotifyCompletion
{
private bool isCompleted = false;
public bool IsCompleted
{
get
{
Console.WriteLine($"Get IsCompleted={isCompleted}");
return isCompleted;
}
}
public void OnCompleted(Action continuation)
{
Console.WriteLine("B.OnCompleted");
continuation();//await的第二部分会封装成一个Action在此执行
isCompleted = true;
}
public int GetResult()
{
Console.WriteLine("B.GetResult");
return 4;
}
}
class Program
{
static A CreateAwaitObject()
{
Console.WriteLine("A.CreateAwaitObject");
var t = new A();
return t;
}
static async void TestAwait(A a)
{
Console.WriteLine("Begin Test");
var result = await a;
Console.WriteLine($"End Test, Resutl:{result}");
}
static void Main(string[] args)
{
var a = CreateAwaitObject();
TestAwait(a);//B.IsCompleted = false;
Console.WriteLine("\n");
TestAwait(a);//B.IsCompleted = true;
Console.ReadKey();
}
}
执行结果:
如果一个对象可以await,这个对象必须实现GetAwaiter函数,可以是成员方法,也可是扩展方法。GetAwaite需要返回一个对象,返回的对象需要满足3个要求:1、实现INotifyCompletion接口;2、实现GetResult函数;3、实现bool IsCompleted,get属性。
从上面例子的执行结果可以很容易看出各个部分的执行顺序,程序执行到await时首先会调用对象的GetAwaiter函数函数,然后调用GetAwaiter返回对象的IsCompleted属性,当IsCompleted=falase时,会调用GetAwaiter返回对象的OnCompleted函数,OnCompleted函数有一个Action参数,这个参数也await将函数分割后第二部分的代码;当IsCompleted=true时,会直接调用await将函数封装后第二部分的代码。
3.await与Task
await对象执行顺序与对象的IsCompleted属性有关,在await Task对象时,执行顺序也与Task是否已经完成有关。当Task. IsCompleted=false时,这个时候会调用TaskAwaiter.OnCompleted函数,OnCompleted内部会调用Task.ContinueWith函数,传入OnCompleted的Action参数。这样当Task执行完成后就会继续执行函数中await Task的第二部分的代码。当Task. IsCompleted=true时,程序会直接继续执行await Task的第二部分的代码,和同步执行一样。
如下:
class Program
{
public static async Task<int> AwaitTask1()
{
var t = Task.FromResult(5);
var r = await t;
Console.WriteLine("Task {0}", Thread.CurrentThread.ManagedThreadId);
return r;
}
public static async Task<int> AwaitTask2()
{
Task<int> t = Task.Run(() =>
{
Console.WriteLine("Task {0}", Thread.CurrentThread.ManagedThreadId);
Task.Delay(2000).Wait();
return 5;
});
var r = await t;
Console.WriteLine("Task {0}", Thread.CurrentThread.ManagedThreadId);
return r;
}
static void Main(string[] args)
{
Console.WriteLine("Main1 {0}", Thread.CurrentThread.ManagedThreadId);
Thread t = new Thread(() => {
Console.WriteLine("Thread {0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("\n");
AwaitTask1().Wait();
Console.WriteLine("\n");
AwaitTask2().Wait();
});
t.Start();
Console.Read();
}
}
结果: