05 - ETTask与同步上下文之间的关系
在 ET 框架中,MainThreadSynchronizationContext
的作用是确保所有的异步任务都在主线程中执行,从而实现单线程协程模型。这个类的核心功能是通过 同步上下文(SynchronizationContext
) 来控制异步任务的执行线程。
1. MainThreadSynchronizationContext
的作用
MainThreadSynchronizationContext
的主要职责是:
- 设置同步上下文:将当前的同步上下文设置为
ThreadSynchronizationContext
,确保所有的异步任务都在主线程中执行。 - 任务调度:通过
Post
方法将任务调度到主线程执行。 - 更新任务队列:在每帧的
Update
方法中处理任务队列,执行所有待处理的任务。
2. 同步上下文的作用
同步上下文(SynchronizationContext
)是 .NET 中用于控制异步任务执行线程的机制。它的核心功能是:
- 线程切换:在异步任务完成后,将回调方法调度到指定的线程执行。
- 线程一致性:确保异步任务的后续逻辑在同一个线程中执行,避免多线程竞争。
在 ET 框架中,同步上下文的作用是确保所有的异步任务都在主线程中执行,从而实现单线程协程模型。
3. 如果不设置同步上下文会怎样?
如果不设置同步上下文(即不调用 SynchronizationContext.SetSynchronizationContext
),异步任务的回调可能会在其他线程中执行,导致以下问题:
- 多线程竞争:异步任务的后续逻辑可能会在多个线程中执行,导致数据竞争和不一致。
- 协程失效:ET 框架的单线程协程模型依赖于所有任务都在主线程中执行,如果不设置同步上下文,协程的挂起和恢复可能会失效。
- 性能下降:多线程的上下文切换和锁竞争会增加性能开销。
4. 为什么 ET 框架需要同步上下文?
ET 框架的核心设计是单线程协程模型,所有的逻辑都在主线程中执行。为了实现这一点,ET 框架需要确保:
- 任务调度:所有的异步任务都在主线程中调度和执行。
- 线程一致性:异步任务的后续逻辑都在主线程中执行,避免多线程竞争。
同步上下文是实现这一目标的关键机制。
5. 如何设置同步上下文
在 ET 框架中,同步上下文的设置是通过 MainThreadSynchronizationContext
的构造函数完成的:
1
2
3
4
public MainThreadSynchronizationContext()
{
SynchronizationContext.SetSynchronizationContext(this.threadSynchronizationContext);
}
- 这行代码将当前的同步上下文设置为
ThreadSynchronizationContext
,确保所有的异步任务都在主线程中执行。
6. 如果不设置同步上下文,ETTask 还能作为单线程协程吗?
如果不设置同步上下文,ETTask
的行为将不再符合单线程协程模型,具体表现如下:
- 回调可能在其他线程执行:异步任务的回调可能会在任务完成时的线程中执行,而不是主线程。
- 协程失效:
await
挂起的协程可能无法正确恢复,导致逻辑错误。 - 多线程竞争:异步任务的后续逻辑可能会在多个线程中执行,导致数据竞争和不一致。
因此,如果不设置同步上下文,ETTask
将无法作为单线程协程正常工作。
7. 示例代码分析
以下是 MainThreadSynchronizationContext
的完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainThreadSynchronizationContext: Singleton<MainThreadSynchronizationContext>, ISingletonUpdate
{
private readonly ThreadSynchronizationContext threadSynchronizationContext = new ThreadSynchronizationContext();
public MainThreadSynchronizationContext()
{
SynchronizationContext.SetSynchronizationContext(this.threadSynchronizationContext);
}
public void Update()
{
this.threadSynchronizationContext.Update();
}
public void Post(SendOrPostCallback callback, object state)
{
this.Post(() => callback(state));
}
public void Post(Action action)
{
this.threadSynchronizationContext.Post(action);
}
}
SynchronizationContext.SetSynchronizationContext
:设置同步上下文,确保所有的异步任务都在主线程中执行。Update
:在每帧的主循环中调用,处理任务队列。Post
:将任务调度到主线程执行。
8. 总结
- 同步上下文的作用:确保所有的异步任务都在主线程中执行,实现单线程协程模型。
- 如果不设置同步上下文:异步任务的回调可能会在其他线程中执行,导致多线程竞争和协程失效。
- ET 框架的设计:依赖于同步上下文来实现单线程协程模型,确保高性能和线程一致性。
因此,设置同步上下文是 ET 框架中 ETTask
作为单线程协程正常工作的必要条件。如果不设置同步上下文,ETTask
的行为将不符合单线程协程的预期,在某些和其他异步调用配合使用的时候可能会不及预期,详细见下面的补充内容。
补充
在仅仅考虑 ETTask
自身的使用,并不与其他异步调用(例如 C# 原生 Task
或其他并行处理方式)配合的情况下,确实可以在一定程度上不设置同步上下文。这主要依赖于 ETTask
的实现方式和目的。以下是一些关键的点来解释这一现象:
1. ETTask 的状态机实现
状态机启动:
ETTask
在创建时可以直接启动自己的状态机,而不依赖外部的SynchronizationContext
。这意味着ETTask
的内部逻辑可以在定义的上下文中执行,而不需特别的同步上下文。内部管理机制:在
ETTask
中,状态机的状态和回调可以被内部保存,以便在后续条件满足时继续执行。因此,即使没有外部上下文设置,ETTask
也能独立进行状态管理。
2. 但执行的上下文仍然重要
尽管在理论上可以不设置同步上下文,具体的执行效果取决于如何使用 ETTask
:
Unity 环境:如果在 Unity 环境中使用
ETTask
,通常推荐设置SynchronizationContext
以确保所有涉及 Unity API 的操作(如访问游戏对象、更新 UI)在主线程中安全执行。异常处理和恢复:如果没有设置同步上下文,返回到原来的上下文就可能存在问题。同步上下文有助于确保任务的回调和异常处理能够在期望的线程上执行,特别是当你从 UI 线程切换到后台工作线程或其他情况时。
3. 灵活性
在某些使用场景下,如果你完全控制了 ETTask
的创建和运行(例如在一个非 Unity 的控制台应用或服务端应用中),而且你的操作不涉及 Unity API 或多线程交互,那么实际上,设置同步上下文可能是多余的。
4. 总结
可以不设置同步上下文:在仅考虑
ETTask
自身的使用时,可以不设置同步上下文,特别是在你明确知道任务不涉及任何需要主线程执行的操作时。推荐设置:在 Unity 环境中,或在更复杂的应用环境中,设置同步上下文是一个良好的实践,以确保代码在适当的上下文中安全运行。
最后的建议
如果你的使用场景不需要任何外部上下文,且 ETTask 的设计目标是满足你的需求,那么在该情况下不设置同步上下文是合理的。然而,为了避免潜在的问题,特别是在多线程或者涉及 UI 操作的环境中,建议常规使用时仍然设置同步上下文以保持灵活性和安全性。