文章

04 - 多线程单线程与Task ETTask await async的关系

04 - 多线程单线程与Task ETTask await async的关系

在 ET 框架中,多线程单线程TaskETTaskawaitasync 的关系是一个非常重要的话题。ET 框架的设计目标是高性能和低开销,因此在处理异步任务时,它采用了独特的单线程协程模型,同时结合了 Taskawaitasync 的语法糖来简化异步编程。下面我们将详细分析它们之间的关系和工作机制。


1. ET 框架的单线程协程模型

ET 框架的核心设计思想是 单线程协程,即所有的逻辑都在一个主线程中运行,通过协程的方式实现异步操作。这种设计有以下优点:

  1. 避免线程切换开销:单线程模型不需要频繁切换线程上下文,减少了性能开销。
  2. 简化并发编程:单线程模型避免了多线程编程中的锁竞争、死锁等问题。
  3. 高性能:通过事件驱动和协程调度,ET 框架能够高效地处理大量并发任务。

单线程与协程的关系

  • 单线程:所有的逻辑都在一个线程中运行,没有多线程的并发执行。
  • 协程:通过 asyncawait 实现异步任务的挂起和恢复,协程的本质是状态机。

在 ET 框架中,协程的调度由主循环(Update 方法)驱动,所有的异步任务都在主线程中通过协程的方式执行。


2. Taskawaitasync 的作用

Taskawaitasync 是 C# 提供的异步编程语法糖,ET 框架基于这些语法糖实现了自己的异步任务模型(ETTask)。

(1) asyncawait

  • async:标记一个方法为异步方法,编译器会将其转换为状态机。
  • await:挂起当前异步方法,等待任务完成后再恢复执行。

在 ET 框架中,await 的作用是挂起当前协程,将控制权交还给主循环,直到任务完成后再恢复执行。

(2) TaskETTask

  • 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 框架中,多线程单线程Taskawaitasync 的关系如下:

  1. 单线程协程:ET 框架的核心是单线程协程模型,通过 ETTaskawait 实现异步任务的挂起和恢复。
  2. 多线程协作:ET 框架允许将多线程任务封装为 ETTask,并通过回调或事件与主线程通信。
  3. TaskETTaskTask 是 C# 原生的多线程任务类型,而 ETTask 是 ET 框架自定义的单线程任务类型,更适合游戏开发的高性能需求。

通过单线程协程模型和 await 语法糖,ET 框架实现了高效的异步编程,同时避免了多线程编程的复杂性。希望这个解读能帮助你更好地理解 ET 框架的设计思想!

本文由作者按照 CC BY 4.0 进行授权