04 - 多线程单线程与Task ETTask await async的关系
在 ET 框架中,多线程、单线程 与 Task、ETTask、await、async 的关系是一个非常重要的话题。ET 框架的设计目标是高性能和低开销,因此在处理异步任务时,它采用了独特的单线程协程模型,同时结合了 Task、await 和 async 的语法糖来简化异步编程。下面我们将详细分析它们之间的关系和工作机制。
1. ET 框架的单线程协程模型
ET 框架的核心设计思想是 单线程协程,即所有的逻辑都在一个主线程中运行,通过协程的方式实现异步操作。这种设计有以下优点:
- 避免线程切换开销:单线程模型不需要频繁切换线程上下文,减少了性能开销。
- 简化并发编程:单线程模型避免了多线程编程中的锁竞争、死锁等问题。
- 高性能:通过事件驱动和协程调度,ET 框架能够高效地处理大量并发任务。
单线程与协程的关系
- 单线程:所有的逻辑都在一个线程中运行,没有多线程的并发执行。
- 协程:通过
async和await实现异步任务的挂起和恢复,协程的本质是状态机。
在 ET 框架中,协程的调度由主循环(Update 方法)驱动,所有的异步任务都在主线程中通过协程的方式执行。
2. Task、await 和 async 的作用
Task、await 和 async 是 C# 提供的异步编程语法糖,ET 框架基于这些语法糖实现了自己的异步任务模型(ETTask)。
(1) async 和 await
async:标记一个方法为异步方法,编译器会将其转换为状态机。await:挂起当前异步方法,等待任务完成后再恢复执行。
在 ET 框架中,await 的作用是挂起当前协程,将控制权交还给主循环,直到任务完成后再恢复执行。
(2) Task 和 ETTask
Task:C# 原生的异步任务类型,通常用于多线程场景。ETTask:ET 框架自定义的异步任务类型,基于单线程协程模型实现。
ETTask 的设计目标是轻量高效,避免了 Task 的多线程开销,同时提供了类似的 await 支持。
3. 多线程与单线程的协作
虽然 ET 框架的核心是单线程协程模型,但在某些场景下,仍然需要与多线程协作。以下是多线程与单线程的协作方式:
(1) 多线程任务的封装
ET 框架允许将多线程任务封装为 ETTask,以便在主线程中通过 await 等待其完成。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static ETTask RunOnThreadPool(Func<Task> action)
{
ETTask tcs = ETTask.Create(true);
ThreadPool.QueueUserWorkItem(async _ =>
{
try
{
await action();
tcs.SetResult();
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs;
}
- 将多线程任务封装为
ETTask,在主线程中通过await等待其完成。
(2) 主线程与多线程的通信
ET 框架通过主循环(Update 方法)驱动协程调度,多线程任务完成后可以通过回调或事件通知主线程。例如:
1
2
3
4
5
public static void CompleteOnMainThread(Action action)
{
// 在主线程的下一个 Update 中执行 action
MainThreadScheduler.Schedule(action);
}
- 多线程任务完成后,通过
MainThreadScheduler将回调调度到主线程执行。
4. ETTask 的工作机制
ETTask 是 ET 框架中异步任务的核心实现,其工作机制如下:
(1) 任务的创建
1
2
3
4
5
6
7
8
public static ETTask Create(bool fromPool = false)
{
if (!fromPool)
{
return new ETTask(); // 创建新实例
}
return queue.Dequeue(); // 从对象池中取出实例
}
ETTask支持对象池,减少内存分配。
(2) 任务的挂起与恢复
1
2
3
4
5
6
7
8
9
public void UnsafeOnCompleted(Action action)
{
if (this.state != AwaiterStatus.Pending)
{
action?.Invoke();
return;
}
this.callback = action; // 注册回调
}
- 当
await一个未完成的ETTask时,UnsafeOnCompleted会将回调(MoveNext方法)保存到callback字段中。
(3) 任务的完成
1
2
3
4
5
6
7
public void SetResult()
{
this.state = AwaiterStatus.Succeeded;
Action c = this.callback as Action;
this.callback = null;
c?.Invoke(); // 触发回调,恢复协程
}
- 当任务完成时,调用
SetResult触发回调,恢复协程的执行。
5. 多线程与单线程的对比
| 特性 | 多线程 (Task) | 单线程 (ETTask) |
|---|---|---|
| 线程模型 | 多线程并发 | 单线程协程 |
| 性能开销 | 线程切换、锁竞争 | 无线程切换,低开销 |
| 并发编程复杂度 | 需要处理锁、死锁等问题 | 无需处理锁,简化并发编程 |
| 适用场景 | CPU 密集型任务、IO 密集型任务 | 游戏逻辑、事件驱动任务 |
| 内存占用 | 每个任务占用一个线程栈 | 任务共享主线程栈,内存占用低 |
6. 总结
在 ET 框架中,多线程、单线程 与 Task、await、async 的关系如下:
- 单线程协程:ET 框架的核心是单线程协程模型,通过
ETTask和await实现异步任务的挂起和恢复。 - 多线程协作:ET 框架允许将多线程任务封装为
ETTask,并通过回调或事件与主线程通信。 Task与ETTask:Task是 C# 原生的多线程任务类型,而ETTask是 ET 框架自定义的单线程任务类型,更适合游戏开发的高性能需求。
通过单线程协程模型和 await 语法糖,ET 框架实现了高效的异步编程,同时避免了多线程编程的复杂性。希望这个解读能帮助你更好地理解 ET 框架的设计思想!