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 框架的设计思想!