Task原理
1、async关键字和await是配套使用的异步方法语法糖,配合Task类可以使多线程变得有序,也可以自己实现一套协程功能,具体可参考:项目地址
2、这里首先介绍一下异步执行原理,后半部分介绍其基本使用。
async await语法糖本质是一个状态机,一般由三种类组成:Builder类,Await类和Task类。这里使用ILSpy反编译工具查看语法糖的本质(C#版本需设置为5.0以前的版本)
一个最简单异步Task用法,async修饰Task
public async Task TaskTest()
{
await Task.Run(() => { });
}
ILSpy反编译后看到的(C#3.0):
[CompilerGenerated]
private sealed class <TaskTest>d__1 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public ClassA <>4__this;
private TaskAwaiter <>u__1;
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
awaiter = Task.Run(delegate
{
}).GetAwaiter();
if (!awaiter.IsCompleted) //await修饰的状态未完成?把控制器交由该类
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<TaskTest>d__1 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter);
num = (<>1__state = -1);
}
awaiter.GetResult();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
[AsyncStateMachine(typeof(<TaskTest>d__1))]
[DebuggerStepThrough]
public Task TaskTest()
{
<TaskTest>d__1 stateMachine = new <TaskTest>d__1();
stateMachine.<>4__this = this;
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create(); //创那就状态机
stateMachine.<>1__state = -1; //初始状态-1
AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine); //启动状态机
return stateMachine.<>t__builder.Task; //返回值
}
可以看到这里使用AsyncTaskMethodBuilder来开启状态机,以async修饰的类指定的Builder(可以使用AsyncMethodBuilder特性来指定Builder,后续会详细说明)创建并开启状态机,并在最后返回值。Task.Run返回的是Task对象(如果async修饰的是void则不需要返回值)
使用await修饰的类(这里是Task)会调用GetAwaiter方法(即被await修饰的类需要有GetAwaiter方法)。C#会为该方法生成一个迭代类,该类会生成一个MoveNext方法(以await关键字为分界线,每个await作为一个状态)。
上述总结:
使用async修饰的类指定的Builder类(使用AsyncMethodBuilder特性指定)开启状态机A并返回值(Builder.Task,无论状态是否完成),
await修饰的类会在MoveNext方法执行GetAwaiter方法获取Awaiter(状态),执行该状态时,如果该状态未完成,则将状态机的控制权交由该状态,该状态完成时继续执行状态机。
使用状态机A迭代调用这些Awaiter,每个Awaiter会判断其IsCompleted属性是否为ture,如为false则调用AwaitUnsafeOnCompleted方法中断调用,我们应当在这个Awaiter完成后继续调用MoveNext方法执行状态机。
总而言之,如果当前状态已完成,则继续调用下一个状态直到完成,如果当前状态未完成,则将控制权(即完成后继续执行的方法)移交给这个未完成的状态,当这个状态确认自己完成后再调用继续执行的方法即可。
自定义STask类,使其能被async和await语法支持
接下来我们自己自定义一个类,使其可以被async await语法糖支持。
还是由三种类组成:Builder,Awaiter和Task。
Builder类会调用Create、Start、AwaitUnsafeOnCompleted等一系列方法,自定义Builder需要定义它们。
Awaiter类需要继承INotifyCompletion接口,在MoveNext中判断了IsCompleted属性以及在最后调用了GetResult方法。
Task类会调用GetAwaiter方法获取Awaiter。
public struct AsyncSTaskMethodBuilder
{ //第一步,创建Builder
public static AsyncSTaskMethodBuilder Create()
{
return default(AsyncSTaskMethodBuilder);
}
private STask task;
public STask Task { get => task; } //最后返回的值(无论状态是否完成)
//第二步:开启状态机
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
stateMachine.MoveNext();
}
//执行完成时调用
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine
{
awaiter.OnCompleted(stateMachine.MoveNext);
}
//该状态尚未执行完毕,获得控制权(MoveNext方法)
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
}
public void SetException(Exception exception)
{
}
public void SetResult()
{
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
[AsyncMethodBuilder(typeof(AsyncSTaskMethodBuilder))]//指定Builder类,async修饰所必须
public class STask
{
public IAwaiter GetAwaiter()//await修饰所必须
{
return new Awaiter(this);
}
//间接继承INotifyCompletion
public struct Awaiter : ICriticalNotifyCompletion
{
private STask coroutine;
internal Awaiter(STask coroutine)
{
this.coroutine = coroutine;
IsCompleted = false;
}
public bool IsCompleted { get; }//判断状态是否完成
//继续执行下一个状态
public void OnCompleted(Action continuation)
{
Console.WriteLine("OnCompleted");
UnsafeOnCompleted(continuation);
}
//等待本状态的事务执行完毕后,调用continuation方法继续执行状态机(保存这个委托)
public void UnsafeOnCompleted(Action continuation)
{
Console.WriteLine("UnsafeOnCompleted");
if (IsCompleted == true)
{
continuation?.Invoke();
return;
}
}
public void GetResult()
{
}
}
}
可以看到,下方代码中,STask 类可以被async await语法糖支持
public async STask TestTask()
{
await new STask();
}
async和await搭配Task类的用法:
延时执行不使用语法糖
public static void PreWaitExecute(Action action,int waitTime)
{
Console.WriteLine("begin execute task");
Task task = Task.Run(() => //创建任务
{
Task delay = Task.Delay(waitTime); //创建子任务,异步等待(不阻塞线程,等待延时任务完成)
delay.Wait();
action.Invoke(); //开始执行方法
});
task.Wait(); //等待任务执行完毕
Console.WriteLine("task finish");
}
返回值可写可不写,如需返回值,只需return T即可,可以使用Task.Result获取return的返回值。
延时执行使用语法糖
public static async void WaitExecute(Action action,int waitTime)
{
Console.WriteLine("begin execute task");
Task task=Task.Run(async ()=>
{
Task delay=Task.Delay(waitTime); //异步等待(创建延时任务,等待延时任务完成)
await delay;
action.Invoke(); //任务开始执行
});
await task; //等待任务执行完毕
Console.WriteLine("task finish");
}
异步获取控制台输入的一行字符
public static async Task<string> ReadInput()
{
string inputStr = null;
await Task.Run(() => //异步等待输入字符
{
inputStr = Console.ReadLine();
});
return inputStr; //返回输入的字符
}
static void Main(string[] args)
{
Task<string> readStrTask = ReadInput();
readStrTask.ContinueWith((Task<string> task) =>
{
Console.WriteLine("str:"+task.Result); //异步输入字符任务完成后打印字符
});
while (true) //保证主线程不会关闭
{
Thread.Sleep(100);
}
}
异步读取执行SQL,读取SQL数据,多返回值
protected async Task<(DbDataReader, DbCommand)> SelectSQLAsync<C>(string sql, params DbParameter[] sqlParams) where C : DbCommand
{
DbCommand cmd = Activator.CreateInstance<C>();
cmd.Connection = dbConn;
cmd.CommandText = sql;
for (int i = 0; i < sqlParams.Length; i++)
{
cmd.Parameters.Add(sqlParams[i]);
}
DbDataReader reader = await cmd.ExecuteReaderAsync();
return (reader, cmd);
}
//测试使用
(DbDataReader reader, DbCommand cmd) = await SelectSQLAsync<MySqlCommand>(sql);