Files
Endorphin-Document/appendices/framework-runtime-lifecycles.md

35 KiB
Raw Permalink Blame History

框架如何接管代码执行

框架通过保存定义、关联入口、管理活动状态和调用用户函数来组织执行。语言、执行引擎、宿主和框架的职责分别说明,才能判断一段代码何时运行、在哪运行,以及哪个实体一直存续。基础结构见12 门语言的执行模型与生命周期,词法用途见关键词手册。

资料核对日期:2026-10-13。本文以普通 hosted C 进程、CPython/FastAPI/ASGI、Java/Spring Boot/Spring MVC、Unity 6.0,以及 TypeScript/React/Node/Chromium 的指定路径为案例。持续更新的官方文档与实现源码用于说明相应机制,不代表所有历史版本。代码展示预期行为,未作为执行日志;框架的开发模式、配置和替代后端会改变部分时序。

先确定每一层回答什么

层 职责 典型证据
语言 调用、作用域、暂停、异常、类型等的语义 Python、Java、C#、ECMAScript 等规范
编译或构建工具 检查和转换源码,准备可执行逻辑 TypeScript 类型擦除、JSX 转换、C# 编译
执行引擎或原生执行环境 推进当前代码并维护必要执行状态 CPython、JVM、V8、Mono、原生机器码
宿主 应用入口、I/O、系统事件、线程或事件循环、呈现能力 ASGI 服务器、Servlet 容器、Node、浏览器、Unity
库或框架 入口映射、应用状态、组件或请求的组织 FastAPI 路由、Spring bean、React 组件树

这些是职责边界,不要求每层都是一个独立进程,也不保证每个产品只处在一层。Unity 同时提供引擎宿主与组件管理;Node 包含 V8、原生绑定及 libuv 等协作部分;浏览器还有渲染、网络及多进程组织,不能缩减成 V8 本身。

注册建立“条件与逻辑”的关系。外部事件或明确调用进入入口;调度机制安排执行机会;引擎执行本次函数。框架也会执行自己的代码,它调用用户函数时通常仍使用语言的普通调用机制。

路径一:C 普通进程

从启动到返回

这是一个完整的 hosted C 示例,不依赖应用框架。预期输出 42 并正常返回成功状态。

#include <stdio.h>

static int price(int base) {
    return base + 2;
}

int main(void) {
    int result = price(40);
    printf("%d\n", result);
    return 0;
}
启动程序
  → 操作系统及实现的启动代码准备执行环境
  → 进入 main
  → 用户调用 price,当前线程进入该函数
  → price 返回 42,继续 main
  → printf 产生输出
  → main 正常返回,执行正常退出流程

price 的函数定义不因“程序已经启动”而自己反复执行。这里没有应用任务队列:下一步由当前控制流确定;CPU 最终执行编译后的指令,OS 仍可调度执行线程。C23 草案的 hosted 启动与终止

谁持有状态

实体 保存的信息 结束方式
程序及静态存储 可执行代码与长期数据 正常退出或进程终止
main 调用 result、当前进度及调用关系 返回
price 调用 base 及临时计算 返回 42;典型原生调用状态结束
输出设施 缓冲和 OS 资源 库及退出协议处理

实际 ABI 可以用栈、寄存器和内联等实现调用。语言规定调用结果,具体执行布局由实现决定。添加 GUI 库或 HTTP 服务器后,入口还会增加库管理的事件关系;C 语言本身没有因此改变。

路径二:FastAPI

注册路由与处理请求是两个动作

以下内容是可交给 ASGI 服务器加载的 Python 模块。sleep 用于展示可暂停等待,不代表真实数据库实现。预期请求 GET /price?base=40 得到 JSON 数据 {"price":42}。

import asyncio
from fastapi import FastAPI

app = FastAPI()

@app.get("/price")
async def price(base: int = 40):
    await asyncio.sleep(0.01)
    return {"price": base + 2}

加载模块时执行导入、创建 app、创建函数及应用装饰器;装饰器把路径、方法和函数等关联起来。它不在注册时替未来每个请求执行 price 函数体。请求进入后,框架才使用这份关联选择处理函数。

服务器以 Uvicorn 为例;事件循环可使用 asyncio 或其兼容实现,而非只能使用 CPython 中某个唯一固定循环。Uvicorn 事件循环选择

一次请求经过哪些层

sequenceDiagram
    participant Client as 客户端
    participant Server as ASGI 服务器与事件循环
    participant App as FastAPI 与 Starlette
    participant Handler as 用户 price 协程
    Client->>Server: GET /price?base=40
    Server->>App: 调用应用:scope、receive、send
    App->>App: 路由、参数与依赖处理
    App->>Handler: 调用并推进协程
    Handler->>Server: 可暂停等待
    Note over Handler: 局部状态和继续关系被保留
    Server->>Handler: 条件满足后安排继续
    Handler-->>App: 返回字典
    App->>Server: 生成并发送响应事件
    Server-->>Client: HTTP JSON 响应

图示压缩了中间件和序列化细节。ASGI 的 HTTP scope 对应一次请求,WebSocket scope 则可贯穿整个连接;HTTP keep-alive 不把多个请求合并为同一次处理函数调用。服务器提供协议事件,应用通过可等待的 receive/send 接收与发送。ASGI 规范

结构 保存的信息 管理者与存续
模块及 app、路由定义 应用配置和入口映射 模块/框架,跨多个请求
请求 scope、Request、依赖求值状态 方法、路径、头、输入及本次依赖 服务器/Starlette/FastAPI,围绕请求
coroutine 继续状态 base 等局部状态、等待后的进度 Python 执行机制,围绕本次处理
Task 及事件循环的等待管理 活动状态、等待结果和恢复安排 ASGI 服务器选定的异步环境
响应对象与输出事件 状态码、头及 body 框架/服务器,直至相应发送流程结束

同步函数和异步函数的执行位置

FastAPI 通过框架调用的普通 def 路由函数和同步依赖,通常交给外部线程池;async def 路由在异步调用链中推进。这个区别只适用于它负责调用的设施:用户自己在 async 路由中直接调用普通辅助函数,仍是普通同步调用,不自动被挪到线程池。异步函数中的 CPU 循环或阻塞 I/O 仍能阻碍事件循环。FastAPI 的技术说明

应用、请求和依赖分别清理

应用的 lifespan 上下文管理共享资源:进入部分用于应用接收请求前的准备,退出部分用于关闭。带 yield 的依赖围绕更小的工作范围获取和释放资源。当前文档区分 scope="function" 的函数退出清理与 scope="request" 的响应发送后清理;不要把两者写成一个固定时间点。应用 lifespan、带 yield 依赖的 scope

处理函数返回不意味着应用退出;请求结束不意味着共享连接池必须销毁。失败进入异常处理路径,取消及断连的传播要结合服务器、中间件和被等待操作判断,不能承诺客户端断开后所有业务效果立即停止或回滚。

路径三:Spring Boot

从入口启动容器

基线是使用 Spring MVC 的应用及嵌入式 Servlet 容器,Tomcat 可作为例子。Spring Boot 组织启动与配置;Spring 的 ApplicationContext 管理 bean;Servlet 容器接入 HTTP;JVM 执行 Java 方法。这些不是同一个生命周期。

下面是 example.pricing 包中的 PriceApplication.java 示例,前提是项目具有相应 Spring Boot Web 依赖。预期请求 GET /price?base=40 返回包含 price: 42 的 JSON 对象。

package example.pricing;

import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class PriceApplication {
    public static void main(String[] args) {
        SpringApplication.run(PriceApplication.class, args);
    }

    @RestController
    static class PriceController {
        @GetMapping("/price")
        public Map<String, Integer> price(
                @RequestParam(name = "base", defaultValue = "40") int base) {
            return Map.of("price", base + 2);
        }
    }
}

启动时由容器读取配置、发现并创建所需 bean、注入依赖及调用相应初始化设施。映射注解是供框架使用的元数据;Java 的 @GetMapping 拼写不是新增 Java 关键词,声明它也不等于马上处理请求。Spring Boot 的启动、bean 与依赖注入

请求怎样进入 controller

HTTP 请求
  → Servlet 容器接入并安排处理
  → Filter 等链路
  → DispatcherServlet
  → HandlerMapping 查找匹配的处理器
  → HandlerAdapter 配合参数解析等设施调用 controller 方法
  → JVM 推进本次 Java 调用
  → 返回值处理及消息转换形成 JSON 响应
  → 容器写回客户端

这里选择 @RestController 的数据响应路径;返回视图名的 controller 还会涉及视图解析。URL 没有一个“执行 Java”的特殊开关,服务端配置的分派逻辑决定产生响应的方法。DispatcherServlet、请求处理序列

结构 保存的信息 管理者与存续
JVM 的类、方法元数据 编译后的 Java 逻辑 JVM/类加载器
bean definition 与 ApplicationContext 创建配方、依赖关系及受管理实例 Spring,跨请求或按配置的 scope
请求映射与 handler 元数据 路径、方法、参数及调用入口 Spring MVC,跨请求
Servlet 请求/响应及分派状态 本次 HTTP 数据和处理关联 容器及框架,围绕一次请求
JVM 方法 frame 参数、局部变量及方法进度 本次 Java 调用

默认 singleton bean 在容器内被复用,不是每个请求都创建一个 controller。方法 frame 是本次调用的状态;把请求数据存入复用实例字段会涉及并发访问。这是实例存续与请求存续不同带来的后果。prototype 等 scope 的退出管理也有不同保证,不能认为所有 bean 都由容器完整管理至销毁。Spring bean scope

初始化、请求返回与关闭

Spring 可以调用 @PostConstruct、初始化接口或配置的 init 方法;正常关闭时可调用 @PreDestroy 等销毁设施。Lifecycle/SmartLifecycle 还表达受管理活动的启停。回调由容器在相应条件下调用,不由 Java GC 替代;具体 bean scope 的管理保证需要分别核对。Spring bean 生命周期

请求方法返回后应用继续存续。Boot 的正常退出支持关闭 ApplicationContext 的安排;强制终止不能被当作所有清理钩子都会执行的正常关闭。Spring Boot 应用退出

WebFlux 是另一种请求模型

Spring Boot 并不只代表 Servlet 阻塞模型。WebFlux 使用非阻塞契约、响应式组合及服务器适配;通常用较少的事件循环工作线程推进活动,和 MVC 常见的阻塞处理假设不同。Mono/Flux 保存或组合数据流协议,不等同于每个请求专属的一条 JVM 调用栈。这里的 Reactor Mono 是响应式库类型,与 Unity 的 Mono 执行后端无关。MVC 也能使用异步设施,不能把二者概括成“Java 一定阻塞”和“Java 一定事件循环”。Spring WebFlux 的并发模型

路径四:Unity

执行后端与阶段安排是两个问题

Unity 的 C# 脚本通常先编译为 IL 等内容。Mono 后端提供托管执行与 JIT;IL2CPP 将 IL 转为 C++ 再编译为原生代码。不能直接把 Unity 写成桌面 CoreCLR 应用。无论采用哪条后端路径,引擎依然承担场景与组件管理,以及 PlayerLoop 的阶段调用安排。Unity 脚本后端

开发准备:C# 源码 → IL → Mono 执行路径/IL2CPP 原生编译路径

应用运行:Unity 启动与场景准备
  → 建立 GameObject 与组件实例及关系
  → 根据对象活动、组件启用与阶段规则调用事件函数
  → PlayerLoop 推进输入、物理、脚本更新及其他系统
  → 场景卸载、禁用或销毁触发相应处理

PlayerLoop 是引擎的一组系统阶段,还能通过相关 API 定制;并不是每一帧把项目里所有 C# 函数都运行一次。Unity 事件执行顺序

哪些回调在什么条件下进入

入口 管理条件 不能推出的结论
Awake 组件实例加载、对象激活等符合条件的初始化,一实例调用一次 不是所有不活动对象都在场景加载时立即执行,也不是 C# 普通构造器
OnEnable 对象活动且组件启用时 不是一生只调用一次,可因再次启用进入
Start 启用后首次更新前的一次初始化 不保证与 Awake 同帧;禁用时可能延后
FixedUpdate 固定时间步的物理更新安排 每个显示帧可能零次、一次或多次
Update 符合条件的脚本帧更新 不是全部数据对象的通用钩子
LateUpdate 在 Update 后的相应阶段 不代替任意线程的同步协议
OnDisable 组件或对象退出启用活动等相应情形 禁用和销毁不是同一状态
OnDestroy 满足条件的组件或对象销毁 有适用边界;强制退出不能保证执行

条件详见 Awake、OnEnable、Start、FixedUpdate、Update、LateUpdate、OnDisable、OnDestroy。

以下是应保存为 PriceLabel.cs 并挂在活动 GameObject 上的组件例子。这里用日志观察执行,不代表 UI 的正式绘制实现。

using UnityEngine;

public class PriceLabel : MonoBehaviour
{
    private int price;

    private void Awake() { price = 42; }
    private void OnEnable() { Debug.Log("enabled"); }
    private void Start() { Debug.Log(price); }
    private void Update() { /* 每次符合条件的更新处理 */ }
    private void OnDisable() { Debug.Log("disabled"); }
}

在常规初始化条件下,实例准备→Awake→OnEnable→Start→后续 Update。重新禁用再启用,可以再发生 OnEnable;这不意味着 price 字段被重新创建或 Start 再次执行。同类不同对象的调用顺序不能仅凭源码文件排列推断。

哪些状态跨帧存在

结构 保存的信息 管理者与存续
场景、GameObject 与组件关系 对象组织、活动及启用状态 Unity 引擎,跨帧
脚本实例及字段 price 等长期组件状态 托管对象及引擎对象关联,跨回调
每次 Update 等的方法调用状态 本次局部变量和当前进度 执行后端,一次回调
IEnumerator 状态机与协程等待记录 跨 yield 的局部数据、继续位置及等待条件 编译器表示及 Unity 协程调度
引擎资源与托管引用 场景对象、资源及引用关系 引擎销毁和托管回收分别处理

普通 Update 返回后,下帧是另一次函数调用;Unity 协程 yield return 后则由引擎按等待条件继续迭代状态。StartCoroutine 不自动创建后台线程;同步耗时计算仍可占用主线程。停止条件也取决于对象、组件及协程 API,不能把“组件 disabled”当成所有任务统一取消协议。Unity 协程

路径五:React

构建不是应用运行的中间调度层

开发及构建:
TS/TSX 源码
  → 类型检查、类型处理、JSX 转换及模块处理
  → 供目标宿主执行的 JavaScript

运行:
浏览器:启动 JS → React 组织组件计算 → DOM 提交 → 浏览器呈现
服务器:Node 执行 JS → React 服务端渲染 → 输出 HTML 等响应

转换可以分散在 TypeScript、其他转换器和打包工具中;JSX 也可以先被保留。构建工具运行在 Node,不证明组件随后一定在 Node 执行。React 是正在被执行的库代码,会组织用户组件及更新的调用;TS 类型通常已擦除。TypeScript 的类型擦除、JSX 转换模式

浏览器首次显示与一次点击

下面是客户端入口 TSX 的例子,前提是页面中有 id="root" 的容器,且构建环境提供 React、React DOM 和相应类型声明。它是普通客户端渲染,不含 SSR。

import { useEffect, useState } from "react";
import { createRoot } from "react-dom/client";

function Price() {
  const [price, setPrice] = useState(42);

  useEffect(() => {
    const previousTitle = document.title;
    document.title = `Price: ${price}`;
    return () => { document.title = previousTitle; };
  }, [price]);

  return <button onClick={() => setPrice(p => p + 1)}>{price}</button>;
}

createRoot(document.getElementById("root")!).render(<Price />);

预期首次按钮显示 42,点击后显示 43;提交后的 Effect 同步相应标题。示例的非空断言不构成运行时容器创建,HTML 必须实际提供该节点。调用 root.render 提交渲染请求,也不能据此认为调用返回时所有 DOM 和 Effect 工作已经完成。createRoot 与 render

步骤 谁进入或调用 保存或使用的状态
加载入口 JS 浏览器的脚本加载与求值机制 JS 模块、函数及 React 库
首次 render 请求 入口调用 React root API root、更新及组件关系
组件计算 React 调用 Price 本次调用的局部值,React 持有的 Hook 状态
commit React 调用 DOM 等宿主接口 已提交的树及 DOM 关系
Effect setup React 按 Effect 规则安排 依赖和返回的 cleanup
用户点击 浏览器事件分派进入 React 事件处理,再调用用户处理逻辑 事件、组件关联及更新请求
后续更新 React 处理状态更新,再组织 render/commit 新状态,旧依赖及新依赖
卸载 root 或组件树操作使组件退出 Effect cleanup、宿主节点及关联清理

render 是计算下一份界面描述,commit 才将相应变化应用到宿主。组件函数可以被多次调用;应保持渲染计算纯净,不能把“函数被调用一次”当作“一次业务操作已经提交”的保证。React render 与 commit、组件与 Hook 的纯度要求

状态为什么没有随组件函数返回而消失

React 持有组件关系及 Hook 状态,组件函数每次调用读取当前状态。普通局部变量随调用建立,useState 管理的状态则与 React 管理的组件身份关联,不能当作每次新建的普通局部值。React 状态的保留与重置

结构 职责 所属层
JS 函数、闭包和本次调用状态 执行组件计算及事件函数 JavaScript 引擎
React element 等界面描述 表达本次计算所得结构 React 库数据
Fiber 与 root 组件工作、父子关联、已保存状态、更新及待处理工作 React 当前协调实现
Hook 状态与 Effect 记录 状态、依赖及 cleanup 关联 React 管理结构
DOM 节点与事件设施 浏览器中的宿主呈现及交互 浏览器

Fiber 是实现结构,源码包含子节点、相邻节点、更新队列和保存状态等信息。它不等于 V8 调用栈,也不是所有 React API 使用者必须直接创建的对象;字段和布局不是稳定应用 API。React 实现中的 Fiber 类型

React Hook 与 Unity 阶段回调的差别

useState、useEffect 等普通 Hook 必须服从顶层调用与顺序约束,它们不是可以任意在条件中增删的动态回调列表。useEffect 记录的 setup/cleanup 与依赖变化关联;useState 则管理状态,并不代表事件监听。特殊的 use API 有不同的调用规则,不能用“所有 use 开头函数都绝对相同”概括。Hook 规则、use API

Effect 在组件提交后按相关规则同步外部系统;依赖变化时先清理旧关联,再建立新关联,卸载时清理。开发 Strict Mode 会增加用于检查的 setup/cleanup 周期。Effect 与浏览器实际绘制的先后不能简单固定为“每次 paint 之后”;useEffect 与 useLayoutEffect 的契约不同。useEffect 的时序与边界

Unity 主要用引擎命名阶段选择符合条件的组件回调;React 主要用更新、组件计算、提交和依赖关联组织 UI。React 不要求组件每个显示帧都重新计算,也没有为每个普通函数自动提供 Awake/Update。

可选的服务端渲染与 hydration

sequenceDiagram
    participant Browser as 浏览器进程
    participant Server as Node 服务器进程
    participant SSR as 服务器 React 渲染
    participant ClientReact as 浏览器 React
    Browser->>Server: 页面请求
    Server->>SSR: 调用服务端渲染入口
    SSR-->>Server: HTML 流等结果
    Server-->>Browser: HTTP 响应与后续资源
    Browser->>Browser: 解析 HTML 并加载客户端 JS
    Browser->>ClientReact: hydrateRoot 等入口
    ClientReact->>Browser: 关联交互逻辑与已有 DOM

renderToPipeableStream 是 Node 流环境的一条 React 服务端输出路径;浏览器的 hydrateRoot 接管已有的服务端 HTML 并建立客户端交互。初始渲染结果要符合 hydration 的一致性要求,不能把服务器函数对象、闭包或堆地址当成直接转移到浏览器的状态。服务端流渲染、hydrateRoot

服务端和客户端分别拥有自己的 JS 执行环境与对象。Effects 不在服务端渲染时运行;需要客户端的交互逻辑仍由浏览器加载和执行。SSR、React Server Components、服务器函数与框架路由是不同机制;本节追踪传统 SSR/hydration,不把其他机制默认叠加进去。

Node 与 Chromium

请求得到什么与客户端怎么使用分别决定

服务端先按路由或静态资源配置生成响应,客户端再按请求用途、响应信息和自己的规则使用内容。URL 的后缀不能单独决定代码是在服务器执行还是在浏览器执行。

动作 服务端可能怎样生成响应 浏览器接收后的典型路径
导航到页面 URL 静态 HTML 或程序生成 HTML 导航及文档解析、样式、布局和呈现
页面引用 <script src="/app.js"> 读取文件或程序生成 JS 脚本机制在满足加载、类型及策略条件后安排执行
fetch("/app.js") 也可能是同一份 JS 字节 得到 Response,读取为文本不自动执行该文本
fetch("/price") 并解析 JSON 服务端处理函数计算再序列化 创建本地 JS 数据;用户代码决定怎样更新界面

脚本加载和执行规则由 HTML 宿主机制规定;Fetch 返回响应的 API 本身不包含“自动把响应当脚本执行”。直接打开 JS 或 JSON 资源时,显示或下载还受导航、媒体类型和响应头等规则影响,不能假定所有浏览器都采用相同展示界面。HTML script 规则、Fetch 方法

服务器即使返回 JSON,浏览器也可以显示文本;具有特定应用含义的按钮、图表等需要客户端程序、HTML 或相应查看器的呈现规则。JSON 的数据结构本身不会自动定义完整应用 UI。

浏览器与 Node 的任务管理

维度 Chromium 中的页面 Node 应用
入口 导航、脚本/模块、事件、控制台求值等 入口模块、REPL、服务器及其他 API 回调
系统事件 用户交互、网络、定时器、页面生命周期等 网络、文件、定时器、进程等
调度组织 HTML 任务来源、任务队列、微任务检查点及渲染机会 libuv 阶段、Node 回调边界及微任务处理
呈现 DOM、样式、布局、绘制等浏览器设施 默认没有页面 DOM 和浏览器绘制阶段
长期状态 文档、DOM、监听关联、JS 环境、框架状态 模块、服务器、连接、监听关联、JS 环境
实际执行 JS V8 V8

浏览器规范中的正式概念是 task;“宏任务”是常见教学叫法。不能把浏览器描述成唯一全局 FIFO 队列。微任务检查点会处理新加入的微任务,渲染还要符合渲染机会条件。HTML 事件循环

Node 的 timers、poll、check 等阶段来自宿主机制;setImmediate 和 process.nextTick 不是 ECMAScript 关键词或 V8 自带的全部循环。nextTick 的先后还要考虑调用上下文及模块求值方式,不能承诺它在所有情境绝对先于一切 Promise 工作。Node 阶段与 nextTick

V8 提供上下文、代码执行和微任务相关能力,宿主通过嵌入接口配合调度。因此“V8 完全没有任何队列”也不准确;完整 I/O 与应用事件循环仍不能统称为 V8。V8 嵌入说明、V8 MicrotaskQueue API

同步调用、事件和 REPL 三种入口

Node HTTP 服务器把请求函数登记到服务器的 request 事件关系中;收到请求后,宿主和服务器设施进入回调,V8 执行用户 JS。运行到普通 helper() 调用时则立即进入该函数,不等待另一个 HTTP 事件。监听器是长期关联,每次调用状态是短期上下文。Node HTTP request 事件

Node REPL 管理输入流、求值上下文、结果输出及会话关闭等;浏览器控制台在所选页面或 frame 的环境中求值,能接触对应 DOM 等宿主对象。它们可以使用同一引擎的求值能力,但会话、绑定规则、可用 API 和后台活动不同。REPL 等输入时,进程不需要一直忙跑一段用户 JS 循环。Node REPL、Chrome 控制台执行 JavaScript

五条路径放在同一张表里

路径 触发与入口选择 执行的推进者 跨执行保留的主要状态 普通工作结束
C 普通进程 启动入口、用户调用 原生代码及 OS 线程调度 程序数据,调用期间的状态 返回或退出
FastAPI 服务器接入、框架路由及依赖 CPython、选定异步环境或线程池 app、路由、请求、Task/协程等待状态 响应及相应范围清理
Spring MVC Servlet 容器、DispatcherServlet JVM 与容器执行安排 bean、映射、请求及方法状态 返回响应;容器继续存续
Unity 引擎状态与 PlayerLoop 阶段 Mono/IL2CPP 路径及引擎 场景、组件字段、协程等待记录 回调返回、禁用或销毁分别发生
React 浏览器 root 更新、状态变化和事件 React 组织调用,V8 执行,浏览器呈现 组件关系、Hook、更新、Effect、DOM 计算完成、提交和卸载分别发生

钩子表达触发关系,调度器安排工作,解释器或其他执行机制推进代码;数据结构保存跨这些动作的关系。状态与迁移规则、归属与释放规则则决定哪些动作合法,以及活动何时结束。它们不能只用“有一个循环”替代。

对 Endorphin 执行模型的设计分析

本节提出研究维度及验证场景,不确认新的语法、原生任务类型或调度 API。现有语言决定仍以 Preflight 与 inflight、函数与调用、Object 与类型系统、资源访问与呈现、REPL 提案和规范状态为准。

先描述可观察的执行契约

维度 需要形成的明确契约 可用的验证场景
定义与活动 定义准备、对象创建、调用开始分别由什么动作发生 查看函数或导入是否触发业务调用
执行入口 显式调用、REPL 输入、界面事件和平台触发怎样进入 提交参数后何时真正执行定价函数
一次执行的单位 本次调用及其子调用、等待和结果如何关联 同一函数同时接受两个不同输入
上下文 代码与依赖、输入、局部状态、进度、结果接收方保存在哪里 等待后继续使用原来的输入
顺序与更新边界 哪些工作必须有序,何时发布可观察结果 同一事件引起两个更新,界面看到什么
等待与继续 保留本次状态、等待条件、恢复入口与失败传播 数据未就绪后继续执行,而非重复业务写入
并发与隔离 同一对象上的活动如何协调,哪些状态可共享 两次执行同时更新同一个运行对象
取消与归属 谁能结束活动,子活动和资源如何跟随 REPL 关闭或 UI 消失后仍在等待的计算
位置与权限 平台怎样决定执行位置和有效能力 同一函数被不同身份使用,跨端对象如何关联
清理与保存 活动结束、资源解除、结果留存和对象回收分别何时发生 查看历史结果不重放计算,订阅结束能释放关联

执行记录可以引用平台对象,但数据对象、函数定义、调用状态、结果句柄、触发关联的职责仍需区分。拥有平台 ID 并不自动意味着需要执行;存储局部变量也不自动意味着具备跨进程恢复能力。持久化正在执行的状态还涉及代码版本、外部操作及恢复边界,不能从“状态可保存”直接推出可靠重启。

用一条轨迹检查规则是否完整

下面是研究用的行为轨迹,不是 Endorphin 源码或新增 API。

定义已准备
  → 用户明确提交一组输入
  → 建立本次执行及有效上下文
  → 获得执行机会并进入函数
  → 遇到未就绪操作,保留进度与等待关联
  → 条件满足后再次获得执行机会
  → 从继续位置推进,交付结果或失败
  → 解除本次活动资源;按规则保留结果或关联

这条轨迹至少能区分“输入还没准备好”“已经执行但正在等待”“已经完成而结果仍被查看”。如果界面重新呈现结果不应重新运行写入,就需要明确结果查看与调用触发的边界;如果不允许自动重试,就不能以重算替代恢复。

再选择队列与钩子的组织方式

以下是设计比较,不是已采用方案。

候选组织方式 适合表达的要求 必须另外处理的边界
直接同步调用 明确调用关系及立即返回 长时间运行、等待及并发活动
固定阶段回调 稳定周期或固定阶段的工作 不活动对象、阶段顺序及启停规则
事件订阅 按事件选择处理逻辑 订阅所有者、取消、重复事件及积压
可暂停任务与继续关系 等待后保留本次计算的进度 调度、公平性、失败、取消及资源归属
组件更新与提交边界 把计算与可观察界面更新关联 可重算性、副作用和结果发布时机
分层队列或检查点 区分不同工作的顺序及更新边界 饥饿、重入、无限连锁更新及跨队列顺序

宏任务/微任务只是一种组织顺序的具体办法。Endorphin 是否需要类似机制,应由事件处理、继续计算和结果呈现之间的顺序要求决定。固定阶段与依赖驱动的更新也可以服务不同实体,不需要先把所有对象放入一个通用循环。

远程数据同步可用于检查跨端身份、变化到达和更新顺序;它只是验证场景之一。执行模型的主问题仍是:入口怎样建立活动,执行如何推进,等待怎样保存并继续,以及活动由谁结束。

12 门语言的执行模型与生命周期 · 关键词手册 · 返回目录