ET框架的事件系统和异步编程
ET框架使用事件的步骤
-
事件类型参数的定义
普通事件(无需用到UnityAPI,有用到UnityAPI的可在ModelView下进行创建EventType)在Model层的
EventType
下定义事件的结构体,结构体内变量即为事件传递时的参数。public struct InstallComputer{ Computer computer; InstallComputer(Computer computer) { this.computer = computer; } }
-
事件的发布
在Hotfix或HotfixView层下(根据逻辑是否用到UnityAPI)进行事件的发布,利用Game.EventSystem.Pulish/PublishAsync函数。
//创建实体 Computer computer = args.ZoneScene.AddChild<Computer>(); //同步发布事件 Game.EventSystem.Publish<EventType.InstallComputer>(new EventType.InstallComputer(computer)); //此种调用方式是启动协程异步调用 Game.EventSystem.PublishAsync<EventType.InstallComputer> (new EventType.InstallComputer(computer)).Coroutine(); //此种是等待事件Run函数返回后才继续向下执行 await Game.EventSystem.PublishAsync<EventType.InstallComputer>(new EventType.InstallComputer(computer));
-
事件的订阅
一般事件的订阅都会放在Hotfix/HotfixView下的实体文件夹下的Event文件夹,采用事件名_功能的命名方式,需要实现AEvent<事件结构体>;
namespace ET { public class InstallComputer_AddComponent : AEvent<EventType.InstallComputer> { //事件处理函数要求返回ETTask,需要使用async配合await protected async override ETTask Run(InstallComputer arg) { //获取computer Computer computer = arg.computer; computer.AddComponent<PCCaseComponent>(); computer.AddComponent<MonitorsComponent>(); computer.AddComponent<KeyBoardComponent>(); computer.AddComponent<MouseComponent>(); computer.Start(); //此写法默认不会进行异步调用,只为符合语法规范 await ETTask.CompletedTask; } } }
这里用到了异步调用的ETTask接下来会进行讲解
ET框架中事件使用的规则和注意事项
-
事件结构体的定义必须在Model或ModelView下,事件的订阅和发布必须在Hotfix或HotfixView下 (是否为View层根据是否需要UnityAPI决定)
-
在ET框架中View层可以直接调用逻辑层的代码,而逻辑层不允许直接调用View层的代码,所以逻辑层想要和View层交互只能使用抛出事件的方式,让View层进行订阅进行相应的处理。
ET框架下ETTask的异步编程
开发早期都是使用协程或者多线程进行程序异步的实现,在C#5.0之后引入了Tasync await等关键字可以轻松优美的实现程序的异步进行。Task是C#异步编程库的异步功能基础类型,包含了多线程的使用。而ET框架主打的是多进程单线程,所以使用ETTask对Task进行了封装,使其不用考虑多线程的共享难题,更易于使用。
协程
协程其实就是创建一段程序辅助主线程的运行,注意协程不是多线程,其仍运行在主线程当中,其只是将一个函数按照一定的条件分解成若干块,穿插在主线程的各个位置中运行。
async 和 await关键字
- async是修饰函数的关键字,被修饰的函数称为异步函数,只有被此关键字修饰的函数才能使用await关键字
- await关键字后跟一些表达式(一般是返回值为ETTask的函数),在异步函数运行到此时会立即跳出函数,继续执行原逻辑。
- await返回前会做出保证,保证await后表达式的任务完成后,执行点会跳回异步函数中,继续向后执行剩余代码
- 若await后表达式正常返回,可用变量接收表达式的返回值,表达式的返回值类型为定义表达式
ETTask<>
的泛型
关键字的本质
实际上就是 依据表达式获取了ET.ETTask<bool>
的awaiter对象,可通过此对象为此表达式发布的OnCompleted事件进行订阅,订阅的内容即为剩余的代码逻辑(注意返回值也属于剩余代码逻辑)
特殊的ETTask.CompletedTask
有时在订阅者的处理函数Run中想使用同步处理,但其又必须使用await关键字,则会考虑在使用await ETTask.CompletedTask
,当运行到这句话不会因为await而跳出去,而是正常向下执行,相当于同步函数。
协程方式调用异步函数
除了await 异步函数()
的方式,还可以使用异步函数().Coroutinue()
以协程的异步方式启动异步函数。
最大区别在于,其不会等待异步函数执行完毕后才继续向下执行。
一般协程调用不会使用返回值,如若使用应检查逻辑错误
ETTask下Await的本质
在异步函数main中遇到await关键字会跳出,等到执行完await后的异步函数sub后才会继续向下执行想必大家已经都了解了。
异步函数sub的运行看似是和原主线程"同时"运行的,实际上就是 调用await sub后,sub异步方法被编译成一个状态机,结合task调度系统,实现语言运行时的协程。
异步方法sub.Coroutine()本质也是将其编译为一个状态机,实现异步的调用,但其不会像await一样跳出当前逻辑,只是单纯的开辟一段协程辅助运行。
本质上无论是await还是.Coroutine() 本质上都是将异步函数编译为一个状态机穿插到主线程中异步调用,两者的区别就是当前函数是否需要等待子异步函数完成才继续向下运行
利用ETTask实现协程 和 Unity的StartCoroutine实现的源码比较:均实现每1秒计算一次
ETTaskTest.cs
public void Test()
{
Calculate().Coroutine();
}
public async ETTask Calculate()
{
while(true)
{
await TimeComponent.Instance.WaitAsync(1000);
int a = 1+1+1;
}
}
CoroutineTest.cs
public void Test()
{
StartCoroutine(Calculate());
}
public async ETTask Calculate()
{
while(true)
{
yield return new WaitForSeconds(1000);
int a = 1+1+1;
}
}