Unity协程层层嵌套如何不翻车?:资深架构师亲授10年实战避坑法则

第一章:Unity协程嵌套的常见陷阱与认知误区

在Unity开发中,协程(Coroutine)是处理异步操作的重要工具,尤其适用于延迟执行、分帧加载或渐进式动画等场景。然而,当多个协程发生嵌套调用时,开发者常常陷入一些不易察觉的陷阱。

误以为嵌套协程会自动等待完成

一个常见的误区是认为通过 StartCoroutine(InnerCoroutine()) 调用内部协程时,外部协程会自动等待其结束。实际上,StartCoroutine 立即返回,不会阻塞执行流。

IEnumerator OuterCoroutine()
{
    Debug.Log("外部协程开始");
    StartCoroutine(InnerCoroutine()); // 不会等待
    Debug.Log("外部协程结束"); // 立刻执行
    yield return null;
}

IEnumerator InnerCoroutine()
{
    yield return new WaitForSeconds(2f);
    Debug.Log("内部协程完成");
}
若需等待,应使用 yield return StartCoroutine()

协程异常难以捕获

嵌套协程中的异常不会向上抛出,导致调试困难。建议在每个协程内部添加异常保护:

IEnumerator SafeCoroutine()
{
    try {
        yield return new WaitForSeconds(1f);
    } catch (System.Exception e) {
        Debug.LogError("协程异常: " + e.Message);
    }
}

重复启动导致逻辑错乱

频繁调用 StartCoroutine 可能引发多个实例并发运行。可通过布尔锁或停止旧协程来避免:
  1. 使用布尔标志位控制执行状态
  2. 在启动前调用 StopCoroutine 清理
  3. 考虑使用 CancellationToken 实现取消机制
问题类型典型表现解决方案
未正确等待逻辑顺序错乱使用 yield return StartCoroutine()
资源竞争状态覆盖加锁或去重启动

第二章:协程嵌套的核心机制解析

2.1 协程执行流程与Yield指令剖析

协程通过暂停与恢复机制实现协作式多任务处理,其核心在于 yield 指令的控制权移交。
执行流程解析
当协程遇到 yield 时,函数状态被保存并返回当前值,控制权交还调度器。后续恢复时从断点继续执行。
func generator() chan int {
    ch := make(chan int)
    go func() {
        for i := 0; i < 3; i++ {
            ch <- i      // 类似 yield 行为
        }
        close(ch)
    }()
    return ch
}
上述代码通过 goroutine 与 channel 模拟 yield 语义:每次发送值后挂起,等待接收方消费后恢复。
状态机与指令调度
协程底层常以状态机实现,yield 触发状态切换,记录下一条指令位置,确保后续执行连续性。
阶段操作
初始协程启动,运行至 yield
挂起保存上下文,移交控制权
恢复恢复寄存器与栈,继续执行

2.2 嵌套层级对主线程调度的影响

在现代前端应用中,组件的嵌套层级深度直接影响主线程的任务调度效率。深层嵌套会导致渲染树重建时递归路径变长,阻塞用户交互响应。
重绘与回流的累积效应
每层嵌套都可能触发独立的布局计算,导致回流叠加:

.container {
  display: flex;
  justify-content: center;
}
.nested-wrapper {
  padding: 16px;
  margin: 8px;
}
上述样式在多层嵌套下会放大重排开销,尤其在动画过程中易引发帧率下降。
任务分片优化策略
使用 requestIdleCallback 拆分深层更新任务:
  • 将渲染任务按层级切分为微任务
  • 优先处理可视区域内的节点更新
  • 利用时间切片避免长时间占用主线程

2.3 StartCoroutine与StopCoroutine的生命周期管理

在Unity中,StartCoroutineStopCoroutine是协程生命周期控制的核心方法。协程允许将任务分步执行,常用于延迟操作、异步加载等场景。
协程的启动与终止
通过StartCoroutine启动协程后,其执行受MonoBehaviour生命周期影响;而StopCoroutine可显式终止指定协程,避免冗余执行。

IEnumerator LoadingSequence() {
    yield return new WaitForSeconds(1f); // 延迟1秒
    Debug.Log("加载完成");
}

// 启动与停止
Coroutine loadOp = StartCoroutine(LoadingSequence());
StopCoroutine(loadOp); // 有效终止
上述代码中,LoadingSequence为返回IEnumerator的协程函数,yield return定义暂停点。使用变量保存返回的Coroutine对象,确保能精准调用StopCoroutine
管理策略对比
  • 使用字符串名称停止:易出错,不推荐
  • 持有Coroutine引用:类型安全,精确控制
  • 自动结束机制:依赖条件判断或yield break

2.4 协程异常传播与错误隐藏问题

在协程编程中,异常的传播机制与传统同步代码存在显著差异。由于协程的异步执行特性,未捕获的异常可能不会立即中断主线程,导致错误被“隐藏”。
异常传播路径
当子协程抛出异常时,若未使用 supervisorScope,异常会向上抛给父协程,进而可能导致整个协程树取消。例如:

launch {
    launch {
        throw RuntimeException("Error in child")
    }
}
上述代码中,子协程异常将终止父协程,影响其他并行任务。
错误隐藏场景
使用 async 时,异常被封装在 Deferred 对象中,直到调用 await() 才会抛出,容易因遗漏调用而导致错误被忽略。
  • 异常在 async 中被延迟暴露
  • 使用 supervisorScope 可隔离异常影响

2.5 使用Reference追踪嵌套协程状态实践

在复杂的异步系统中,嵌套协程的状态管理极易失控。通过引入Reference机制,可有效追踪协程生命周期与状态变更。
Reference的设计原理
Reference对象充当协程间共享的“状态句柄”,允许多层协程读取或监听统一状态源,避免信息孤岛。

type CoroutineRef struct {
    State  int32
    Cancel context.CancelFunc
}

func spawnNested(ctx context.Context, ref *CoroutineRef) {
    go func() {
        defer atomic.StoreInt32(&ref.State, 2)
        atomic.StoreInt32(&ref.State, 1)
        // 执行异步任务
    }()
}
上述代码中,CoroutineRef 封装了状态码与取消函数,父协程可通过 ref.State 实时感知子协程进度。原子操作确保状态更新线程安全。
状态值语义约定
  • 0: 初始态
  • 1: 运行中
  • 2: 已完成
  • -1: 被取消
该约定提升协作可读性,便于调试与监控系统集成。

第三章:避免阻塞与资源泄漏的设计模式

3.1 利用标志位与状态机控制协程并发

在高并发场景中,协程的执行需精确控制。使用标志位可实现简单的启停控制,而状态机则能管理复杂生命周期。
标志位控制示例
var running bool
var mu sync.Mutex

func worker() {
    for {
        mu.Lock()
        if !running {
            mu.Unlock()
            return
        }
        mu.Unlock()
        // 执行任务
    }
}
通过互斥锁保护的布尔标志 running,可安全地启动或终止协程,避免竞态条件。
状态机驱动的协程管理
使用状态机可定义协程的多个阶段,如待命、运行、暂停、终止。
状态行为
Idle等待启动信号
Running执行核心逻辑
Paused临时挂起
Stopped永久退出
状态转换由事件触发,确保协程行为可预测且易于调试。

3.2 使用CancellationToken模拟协程取消机制

在异步编程中,及时释放资源和终止无用任务至关重要。CancellationToken 提供了一种协作式取消机制,允许一个或多个线程安全地请求取消操作。
取消令牌的工作原理
CancellationToken 本身不执行取消,而是作为信号传递工具。当调用 CancellationTokenSource 的 Cancel() 方法时,所有关联的 CancellationToken 将进入“已取消”状态,并触发注册的回调。
var cts = new CancellationTokenSource();
var token = cts.Token;

Task.Run(async () =>
{
    while (!token.IsCancellationRequested)
    {
        Console.WriteLine("协程运行中...");
        await Task.Delay(500, token);
    }
}, token);

// 模拟外部取消
await Task.Delay(2000);
cts.Cancel(); // 触发取消
上述代码中,Task.Delay(500, token) 在取消时会抛出 OperationCanceledException,实现优雅退出。通过轮询 IsCancellationRequested 或传递 token 到可取消方法,能有效模拟协程的取消行为。
  • CancellationToken 是轻量级、线程安全的结构体
  • 推荐将 token 作为参数传递给所有支持取消的异步方法
  • 使用 using 语句管理 CancellationTokenSource 生命周期

3.3 避免重复StartCoroutine的防抖设计

在Unity开发中,频繁调用`StartCoroutine`可能引发协程堆积,导致逻辑重复执行或性能下降。通过引入防抖机制,可确保协程在指定间隔内仅启动一次。
协程防抖核心逻辑
private Coroutine debounceCoroutine;
private float debounceDelay = 0.5f;

public void DebouncedAction()
{
    if (debounceCoroutine != null)
        StopCoroutine(debounceCoroutine);
    
    debounceCoroutine = StartCoroutine(ExecuteWithDelay());
}

private IEnumerator ExecuteWithDelay()
{
    yield return new WaitForSeconds(debounceDelay);
    // 执行目标逻辑
    Debug.Log("Action executed");
    debounceCoroutine = null;
}
上述代码通过维护一个`Coroutine`引用,在下次调用前主动终止未完成的协程,从而避免重复执行。`debounceDelay`控制延迟时间,适用于按钮响应、输入反馈等高频触发场景。
适用场景对比
场景是否需要防抖说明
UI按钮点击防止用户快速连点触发多次请求
定时数据轮询需稳定周期性执行

第四章:实战中的安全嵌套策略与优化技巧

4.1 分层解耦:将复杂嵌套拆解为可复用协程单元

在高并发场景中,原始的嵌套协程逻辑易导致维护困难。通过分层设计,可将业务流拆解为独立、可复用的协程单元。
职责分离的协程模块
将数据获取、处理、写入等操作封装为独立函数,每个函数启动自身协程并返回结果通道。
func fetchData() <-chan []byte {
    out := make(chan []byte)
    go func() {
        // 模拟网络请求
        data := httpGet("/api/data")
        out <- data
        close(out)
    }()
    return out
}
该函数封装了数据获取逻辑,外部只需接收其返回通道,无需关心内部执行细节。
组合协程单元
使用管道连接多个协程单元,形成清晰的数据流:
  • fetchData:负责异步获取原始数据
  • transformData:对数据进行格式转换
  • saveData:持久化处理结果
各层之间通过 channel 通信,实现松耦合与高内聚,显著提升代码可测试性与复用性。

4.2 封装通用异步任务链处理工具类

在高并发系统中,异步任务的有序执行与状态管理是关键挑战。为提升代码复用性与可维护性,需封装一个通用的任务链处理器。
核心设计思路
通过定义任务接口和链式调度器,实现任务的注册、串行/并行执行与错误传递。
type AsyncTask func() error

type TaskChain struct {
    tasks []AsyncTask
}

func (tc *TaskChain) Add(task AsyncTask) *TaskChain {
    tc.tasks = append(tc.tasks, task)
    return tc
}

func (tc *TaskChain) Execute() error {
    for _, task := range tc.tasks {
        if err := task(); err != nil {
            return err
        }
    }
    return nil
}
上述代码定义了可组合的异步任务链:`Add` 方法用于注册任务,`Execute` 按序触发。每个任务为无参返回 `error` 的函数,便于统一错误处理。该结构支持动态构建流程,适用于数据同步、消息推送等场景。

4.3 结合UnityEvent实现事件驱动型协程调用

在Unity中,将协程与UnityEvent结合可构建高度解耦的事件驱动系统。通过监听UnityEvent触发StartCoroutine,能够实现异步操作的按需执行。
事件绑定协程调用
在组件初始化时,将协程封装为无参委托并注册到UnityEvent:
public UnityEvent onPlayerDeath;
void Start() {
    onPlayerDeath.AddListener(StartRespawnSequence);
}

private IEnumerator StartRespawnSequence() {
    yield return new WaitForSeconds(2f); // 等待2秒
    Debug.Log("Player respawning...");
}
上述代码中,onPlayerDeath 为外部可配置的事件,当其被触发时,自动启动包含等待逻辑的协程。该模式适用于UI反馈、角色死亡处理等场景。
优势与适用场景
  • 解耦事件源与行为逻辑
  • 支持编辑器可视化连接
  • 便于多模块协同响应同一事件

4.4 性能监控:协程堆栈深度与GC压力分析

在高并发场景下,协程的堆栈深度直接影响内存占用与垃圾回收(GC)频率。过深的堆栈会加剧GC压力,导致STW(Stop-The-World)时间变长。
监控协程堆栈深度
可通过 runtime.Stack 获取当前协程堆栈信息:

buf := make([]byte, 1024)
n := runtime.Stack(buf, false)
fmt.Printf("Stack depth: %d bytes\n", n)
该代码捕获当前协程的堆栈快照,n 表示实际使用的字节数,可用于判断是否接近默认堆栈限制(通常为1GB)。
GC压力分析指标
关键指标包括:
  • GC暂停时间(P99应小于50ms)
  • 堆内存分配速率(MB/s)
  • 年轻代晋升率
频繁的 minor GC 往往意味着短生命周期对象过多,建议结合 pprof 分析内存分配热点。

第五章:从协程到UniTask——现代异步方案的演进思考

传统协程的局限性
Unity 中的协程基于 IEnumerator 实现,虽能处理异步流程,但存在明显缺陷:无法返回值、异常处理困难、调试支持差。例如,嵌套多个 yield return 会导致“回调地狱”式代码:

IEnumerator LoadData()
{
    yield return new WaitForSeconds(1f);
    yield return StartCoroutine(FetchUserData());
    yield return StartCoroutine(ProcessData());
}
UniTask 的优势与实践
UniTask 是基于 C# ValueTask 的高性能异步库,专为 Unity 优化。它支持 async/await,显著提升代码可读性与性能。以下为实际加载场景的对比:
特性协程UniTask
语法简洁性中等
异常处理受限完整支持 try/catch
GC 分配每次分配 IEnumerator零 GC(结构体)
迁移案例:资源加载优化
将 AssetBundle 加载从协程迁移到 UniTask,可大幅提升响应速度与维护性:

async UniTask<GameObject> LoadPrefabAsync(string address)
{
    var handle = Addressables.LoadAssetAsync(address);
    await handle.ToUniTask(); // 零开销转换
    return handle.Result;
}
  • 使用 .ToUniTask() 无缝集成 Addressables
  • 结合 UniTask.WhenAll() 并行加载多个资源
  • 利用 PlayerLoopTiming 精确控制执行时机
执行流程示意:
→ 启动异步加载任务
→ 挂起至 PlayerLoop 完成帧
→ 回调调度至主线程继续执行
→ 资源就绪后返回结果
源码下载地址: https://pan.quark.cn/s/a4b39357ea24 谷歌公司设计了一款无费用且具备开源特性的网络浏览器,名为Chrome,因其卓越的速度、稳定性和安全性而广受赞誉。该浏览器运用了前沿的Web渲染引擎Blink以及JavaScript引擎V8,旨在保障网页载入与脚本运行的卓越效能。为应对无网络环境下的Chrome安装需求,特别准备了离线安装包。此压缩文件内含32位与64位两种规格的Chrome浏览器离线安装方案,具体文件名分别为"chromedev_x64-v68.0.3423.2.exe"与"chromedev_x86-v68.0.3423.2.exe"。在文件命名中,"x64"标识64位版本,适用于64位操作系统平台,而"x86"则对应32位版本,适配32位操作系统。文件名中的"v68.0.3423.2"代表Chrome的一个特定版本号,各版本可能涵盖安全补丁、性能改进或新增功能。与32位Chrome相比,64位版本具备如下长处:能够处理更多内存容量,从而提升多任务作业能力;针对现代硬件的优化使其运行更为迅猛;64位版本更具备高级别的安全防护,能更周全地抵御恶意软件的侵袭。尽管如此,32位版本对于仍在使用32位操作系统的用户,或是在系统资源需求高的场景下,依然适用。在部署Chrome浏览器时,用户需依据其个人计算机的操作系统平台,挑选匹配的版本进行安装。通过双击相应的.exe文件,安装流程将自动启动,一般包含接受使用许可、确定安装路径及构建桌面快捷方式等环节。若在安装阶段遭遇难题,可参照提示信息或联系技术支援获取协助,同时该压缩文件发布者亦表明欢迎用户以留言形式反映问题。Chrome浏览器的主要特质涵盖:直观的用户界面设计...
内容概要:本文围绕直驱式永磁同步电机(PMSM)矢量控制系统的建模与仿真展开研究,基于Simulink平台构建了完整的控制系统仿真模型,涵盖了电机本体数学建模、三相/两相坐标变换(Clarke/Park变换)、磁场定向控制(FOC)、电流环与速度环双闭环PID控制策略、空间矢量脉宽调制(SVPWM)技术以及转速调节器设计等核心技术环节。通过仿真实验验证了该控制策略在动态响应速度、稳态运行精度及抗负载扰动能力方面的优良性能,充分体现了矢量控制在实现电机高性能调速中的优势,为永磁同步电机在工业驱动、新能源汽车和高端装备制造等领域的实际应用提供了可靠的理论依据与技术支撑。; 适合人群:具备电机学、电力电子技术和自动控制原理基础知识的电气工程、自动化、机电一体化等相关专业的研究生、高校教师、科研人员,以及从事电机驱动系统、新能源汽车电驱、工业自动化设备研发的工程技术人员。; 使用场景及目标:①深入理解永磁同步电机矢量控制的基本原理与实现机制;②掌握在Simulink中搭建高精度电机控制系统仿真模型的方法与技巧;③为电机控制算法的设计、优化与参数整定提供高效的仿真验证平台;④服务于高校课程设计、毕业课题研究、科研项目前期验证及企业产品开发中的控制策略测试。; 阅读建议:建议结合经典电机控制教材进行对照学习,重点关注各功能模块间的信号流向、反馈机制与参数耦合关系,动手复现并调试仿真模型,通过改变PI参数、负载条件和给定转速等方式观察系统响应,从而深入掌握控制策略的内在逻辑与性能优化方法。
代码下载地址: https://pan.quark.cn/s/a4b39357ea24 Java学习路线(鱼皮)是一个全面且循序渐进的Java开发技能培养方案,该路线从基础入门直至高级应用,致力于协助学习者高效地掌握Java编程的全部核心内容。此学习路线的独特之处在于其新颖性、系统性、实践性、开放性以及社区回馈与持续迭代更新。其核心构成涵盖了预备阶段、Java入门知识、Java进阶技能、Java高级技术、Java框架应用以及Java项目实践等多个学习模块,每个模块均整合了相应的知识点、学习策略与资源指引。在预备阶段,学习者需配置在线编程环境、选择笔记工具、熟悉Markdown文档编写等基本技能,为编程学习奠定基础。在Java入门阶段,学习者应重点掌握Java编程的基础理论、开发环境配置、IDEA集成开发环境的使用、项目创建与执行调试、界面设置及插件配置等关键技能。在Java入门阶段,学习者还须深入理解Java基础语法、数据结构类型、程序流程控制、数组操作、面向对象编程、方法重载机制、封装原则、继承特性、多态表现、抽象类的概念、接口定义、枚举类型、常用类库、字符串处理、日期时间管理、集合框架、泛型编程、注解应用、异常处理机制、多线程技术、IO流操作、反射机制等核心知识点。在Java进阶阶段,学习者需要重点学习Java 8的更新特性、Stream API的应用、Lambda表达式的使用、新的日期时间处理API以及接口默认方法的实现。在Java高级阶段,学习者需要掌握Java框架的应用、Spring Boot框架的搭建、Spring Cloud微服务架构的实施等高级技术。在Java项目阶段,学习者需要学习Java项目开发的全过程操作,包括项目架构设计、项目编码实现、项...
内容概要:本文围绕基于Matlab代码实现的卫星信号传播模拟研究,系统阐述了卫星信号在大气层及空间环境中传播特性的数值仿真方法。研究通过建立精确的数学模型,对信号衰减、传输延迟、多普勒效应以及噪声干扰等关键物理现象进行建模与仿真分析,全面还原实际通信场景下的信号行为特征。该仿真体系仅可用于验证通信链路设计的可靠性,还能为星地链路预算、抗干扰策略优化及接收机算法开发提供理论依据和技术支持。; 适合人群:具备一定Matlab编程能力、通信原理基础和电磁波传播知识的高校研究生、科研机构研究人员及从事卫星通信系统设计与仿真的工程技术人员。; 使用场景及目标:①用于高校课程中卫星通信相关理论的教学演示与实验教学;②支撑航天通信项目的链路性能评估与系统参数优化;③为新型调制解调、纠错编码和信号增强算法的研发提供可验证的仿真平台;④辅助科研人员开展低轨星座、深空探测等前沿领域的通信建模研究; 阅读建议:建议读者结合经典通信理论教材,深入理解各模块的物理意义,动手运行并调试提供的Matlab代码,尝试调整轨道参数、大气模型和噪声水平等变量,观察其对信号质量的影响,进而拓展模型以适配同卫星轨道类型或复杂多径环境,提升综合仿真与分析能力。
打开链接下载源码: https://pan.quark.cn/s/a4b39357ea24 ### 常用电流电压检测电路:详细解析与实际应用 在电力电子技术范畴内,电流电压检测电路是达成各类电力设备控制与监测的关键构成部分。本资料将详细研究几种普遍应用的电流电压检测电路,意图辅助读者深入掌握其运行机制、设计要素及实际运用环境。 #### 一、电网电压同步检测电路 电网电压同步检测电路主要致力于完成电力系统中逆变器输出与电网电压之间的精确同步。以DSTATCOM(配电网静态同步补偿装置)为例,其系统硬件主要由主回路、控制回路以及检测与驱动回路三大部分组成。其中,检测电路负责采集3路交流电压、6路交流电流、2路直流电压和2路直流电流,同时还包括电网电压同步信号。 1. **常用电网电压同步检测电路及其特性** - **RC滤波模块**:用于滤除电网电压中的高频杂波,保障电压检测信号的纯净度。例如,在图2-2中,由电阻R5(1KΩ)和电容C4(15pF)构成的RC滤波装置,其时间常数远小于系统输出频率,有效降低了系统与电网的相位偏差。 - **过零比较单元**:如LM311,用于识别电网电压的过零时刻,从而实现电压信号的同步处理。过零比较单元输出的方波信号可用于控制单元的同步操作。 - **上拉限幅与非门电路**:用于强化驱动能力,确保信号符合微控制单元的输入标准,如TMS320LF2407的输入信号标准。 2. **脉宽调制PWM同步信号电路**:基于ADMC401芯片的PWM发生装置,通过PWMSYNC引脚提供与开关频率同步的PWM同步脉冲信号。此电路结合光电隔离元件TLP521与D触发器MC14538,实现精确的过零时刻检测与信号同步。 3. **缓冲与比较单元电路...
源码链接: https://pan.quark.cn/s/976d0efeb74a 最近重装了Windows10,发现风扇转动异常,查看任务管理器发现系统和压缩内存进程占用CPU达20%-30%,在网上查阅了2天资料,找到了解决方法,如是分享出来,让大家更好的使用Windows10系统。 在Windows 10操作系统中,有时用户会遇到一个令人困扰的问题,即“系统”和“压缩内存”进程占用大量的CPU和内存资源,导致计算机性能下降,甚至风扇高速运转,这可能对用户的日常使用体验造成小的影响。 这种情况通常与系统的内存管理机制有关,特别是涉及到Windows的内核组件ntoskrnl.exe。 ntoskrnl.exe是Windows操作系统的核心系统文件,它负责管理和调度系统资源,包括内存管理。 在某些情况下,尤其是系统进行自我优化或内存清理时,这个进程可能会占用大量CPU资源。 而“系统”进程则包含了Windows 10内核及一些基本服务,当它与“压缩内存”进程一同高占用,可能意味着系统正在进行内存压缩以释放空间,或者是因为某些后台活动导致了额外的压力。 要解决这个问题,一种可能的方案是禁用内存自检任务,这个任务可能会在系统空闲时触发,导致必要的CPU和内存负载。 具体步骤如下: 1. 通过搜索栏或控制面板进入“管理工具”。 2. 在管理工具中找到并打开“任务计划程序”。 3. 在任务计划程序库中,导航到“Microsoft” > “Windows” 节点。 4. 在该节点下,你会看到“MemoryDiagnostic”子目录,双击进入。 5. 你会发现有两个与内存诊断相关的任务,通常是“RunFullMemoryDiagnostic”和“RunMemoryDiag...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值