part1 - 【深入浅出C# async/await】编译篇
part2 - 【深入浅出C# async/await】理解 awaitable-awaiter 模式
part3 - 【深入浅出C# async/await】运行时上下文
part4 - 【深入浅出C$ async/await】自定义Task
读完前三章相信大家已经对async,await,task有一个大概的了解。那么接下来,我们尝试以下自己写一个Task。
首先引用Part3末尾的结论,一个对象必须具有以下条件才是可以等待的:
- 它有一个 GetAwaiter() 方法(实例方法或扩展方法)。
- 它的 GetAwaiter() 方法返回一个awaiter。如果满足以下条件,编译器会判定对象是awaiter:
- 它实现了 INotifyCompletion 或 ICriticalNotifyCompletion 接口;
- 它有一个 IsCompleted 属性,它有一个 getter 方法并返回一个Boolean;
- 它有一个 GetResult() 方法,该方法返回 void(对应Task) 或 TResult(对应Task)。
自定义Task模板
public class MyTask : ICriticalNotifyCompletion
{
#region MyTask必须包含以下方法,编译器才会认为MyTask是可等待的
public bool IsCompleted
{
get { return true; } //默认MyTask是已经执行完的
}
public MyTask GetAwaiter()
{
return this; //相当于省略了awaitable,MyTask自己就是一个awaiter
}
public void GetResult()
{
}
#endregion
public void OnCompleted(Action continuation)
{
}
public void UnsafeOnCompleted(Action continuation)
{
}
}
简单测试一下
public class TaskLikeTest
{
public static void Main2_1()
{
TaskTest();
Console.WriteLine("Main");
}
public static async void TaskTest()
{
await new MyTask();
Thread.Sleep(4000);
Console.WriteLine("Test");
}
}
Test
Main
可以看到MyTask实例可以被await。
除此之外,我们可以注意到,TaskTest()实际上并不是一个异步的方法,TaskTest中Thread.Sleep(4000)
会阻塞主线程,这是为什么呢?在Part1中我们曾经对异步方法源码进行过分析,现在反编译程序集看看是怎么回事:
public sealed class TaskTest_1 : IAsyncStateMachine
{
public int state;
public AsyncVoidMethodBuilder builder;
public object u_1;
private void MoveNext()
{
int num = state;
try
{
MyTask awaiter;
if (num != 0)
{
awaiter = new MyTask().GetAwaiter();
if (!awaiter.IsCompleted) //关键
{
num = (state = 0);
u_1 = awaiter;
TaskTest_1 stateMachine = this;
builder.AwaitUnsafeOnCompleted(ref awaiter,ref stateMachine);
return;
}
}
else
{
awaiter = (MyTask)u_1;
u_1 = null;
num = state = -1;
}
awaiter.GetResult();
Thread.Sleep(4000);
Console.WriteLine("Test");
}
catch (Exception e)
{
state = -2;
builder.SetException(e);
return;
}
state = -2;
builder.SetResult();
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
this.builder.SetStateMachine(stateMachine);
}
void IAsyncStateMachine.MoveNext()
{
MoveNext();
}
}
public static void taskTest_1()
{
TaskTest_1 stateMachine = new TaskTest_1();
stateMachine.builder = AsyncVoidMethodBuilder.Create();
stateMachine.state = -1;
stateMachine.builder.Start(ref stateMachine);
}
在反编译的方法中,stateMachine.builder.Start(ref stateMachine)
启动了状态机,但是此时IsCompleted为true,也就是说MyTask已经完成,看代码,我们会同步执行await之后的代码。
总结: 状态机启动 —> 获得MyTask的awaiter —> 发现awaiter.IsCompleted = true —> 执行GetResult()之后的代码。
关于状态机流程的解释,我发现自己写的并不是很好,您可以看这篇文章对状态机流程和AwaitUnsafeOnCompleted进行深入了解:.NET Task 揭秘(2):Task 的回调执行与 await
在Part1中我们用Task.ContinueWith替换了整个状态机,我们把await后面的代码作为回调传入Task.ContinueWith()。AwaitOnCompleted与之同理,我们修改一下MyTask。
public enum AwaiterStatus : byte
{
Pending = 0,
Succeeded = 1,
Faulted = 2
}
public class MyTask : ICriticalNotifyCompletion //和ICriticalNotifyCompletion的区别在于不会同步ExcutionContext
{
public AwaiterStatus state;
public object callback;
#region MyTask必须包含以下方法,编译器才会认为MyTask是可等待的
public bool IsCompleted
{
get { return state!=AwaiterStatus.Pending; } //默认MyTask是已经执行完的
}
public MyTask GetAwaiter()
{
return this; //相当于省略了awaitable,MyTask自己就是一个awaiter
}
public void GetResult()
{
switch (this.state)
{
case AwaiterStatus.Succeeded:
break;
case AwaiterStatus.Faulted:
ExceptionDispatchInfo c = this.callback as ExceptionDispatchInfo;
this.callback = null;
c?.Throw();
break;
//task执行完了状态还是Pending,显然是异常
default:
throw new NotSupportedException("ETTask does not allow call GetResult directly when task not completed.Please use await");
}
}
#endregion
//SetResult设置State,并执行回调
public void SetResult()
{
if (state != AwaiterStatus.Pending)
{
throw new InvalidCastException("TaskT_TransitionToFinal_AlreadyCompleted");
}
state = AwaiterStatus.Succeeded;
Action continuation = this.callback as Action;
continuation?.Invoke();
}
public void OnCompleted(Action continuation)
{
this.UnsafeOnCompleted(continuation);
}
//把await之后的代码作为回调传入
public void UnsafeOnCompleted(Action continuation)
{
if (this.state != AwaiterStatus.Pending)
{
continuation?.Invoke();
return;
}
this.callback = continuation;
}
//通知 C# 编译器将该方法内联处理.
//内联的好处是避免方法调用的额外开销,但是会让编译后的代码体积变大
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[DebuggerHidden]
public void SetException(Exception e)
{
if (this.state == AwaiterStatus.Pending)
{
throw new InvalidCastException("当前状态为success/faulted,不能再转换状态了");
}
this.state = AwaiterStatus.Faulted;
}
}
测试:
public static void Main2_1()
{
TaskTest();
Console.WriteLine("Main");
while (true){}
}
public static async void TaskTest()
{
MyTask tcs = new MyTask();
ThreadPool.QueueUserWorkItem(state =>
{
Thread.Sleep(2000);
tcs.SetResult();
});
await tcs;
Console.WriteLine("Test");
}
Main
Test
Task-Like Property
下面的方法,在编译器中会报错。
对异步方法进行反编译后,能看到对应生成的状态机类里有一个Builder成员。例如上面的TaskTest_1类中,可以看到Builder的类型为AsyncVoidBuilder。
对于我们自定义的Task,C#找不到对应的Builder,因此需要我们自己定义。
如果您想深入了解AsyncMethodBuilder,请看 .NET Task 揭秘(3)async 与 AsyncMethodBuilder
为MyTask添加标签。
[AsyncMethodBuilder(typeof(MyAsyncTaskMethodBuilder))]
//没有builder,MyTask不能作为函数返回值,编译器会报错不是一个task-like类型
MyAsyncTaskMethodBuilder结构如下:
//请结合上面反编译代码中的TaskTest_1阅读
public struct MyAsyncTaskMethodBuilder
{
private MyTask tcs;
//Builder必须有以下方法/成员,不然编译器不能通过编译!!!
//1. 创建方法
[DebuggerHidden]
public static MyAsyncTaskMethodBuilder Create()
{
MyAsyncTaskMethodBuilder builder = new MyAsyncTaskMethodBuilder() { tcs = new MyTask() };
return builder;
}
//2. TaskLike Task property
[DebuggerHidden]
public MyTask Task => this.tcs;
//3. SetException
[DebuggerHidden]
public void SetException(Exception exception)
{
this.tcs.SetException(exception);
}
//4. SetResult
[DebuggerHidden]
public void SetResult()
{
this.tcs.SetResult();
}
//5. AwaitOnCompleted
//不会用到这个方法,因为我们不需要同步executionContext
[DebuggerHidden]
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine
{
awaiter.OnCompleted(stateMachine.MoveNext);
}
//6.AwaitUnsafeOnCompleted --- 可以看Task的
[DebuggerHidden]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
awaiter.OnCompleted(stateMachine.MoveNext);
}
//7. start启动状态机
[DebuggerHidden]
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
stateMachine.MoveNext();
}
//8. SetStateMachine
[DebuggerHidden]
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
测试
public static void Main2_1()
{
TaskTest3();
Console.WriteLine("Main");
while (true)
{
}
}
static async void TaskTest3()
{
await TaskTest2();
Thread.Sleep(1000);
Console.WriteLine("TaskTest3");
}
static async MyTask TaskTest2()
{
await Task.Delay(3000);
Console.WriteLine("TaskTest2");
}
Main
TaskTest2
TaskTest3