扩展实现Unity协程的完整栈跟踪
现如今Unity中的协程(Coroutine)方案已显得老旧,Unitask等异步方案可以做到异常捕获等yield关键字处理起来很麻烦的问题,
并且Unity官方也在开发一套异步方案,但对于临时加入到项目等情况,还是要解决协程的一些问题。
Unity协程中无法输出完整的栈跟踪,因为协程编译后会转换为IL编码的状态机,中间存在栈回到堆的过程,因此
在有多干yield函数嵌套的协程中报错,看到的栈信息一般会是缺失的:
代码:
public class TestClass : MonoBehaviour {
private void Start() {
StartCoroutine(A());
}
private IEnumerator A() {
yield return B();
}
private IEnumerator B() {
yield return C();
yield return null;
}
private IEnumerator C() {
yield return null;
Debug.Log("C");
}
}
输出(栈信息丢失):
C
UnityEngine.Debug:Log (object)
TestClass/<C>d__3:MoveNext () (at Assets/TestClass.cs:31)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)
似乎要比较好的解决这个问题,只能拿到MoveNext()重新封装协程的执行流程,或直接使用async方案。
不过那样就太重了,经过摸索后发现,还是存在一些方法,可以简单的实现完整栈信息的获取。
1.StackTrace类打印栈跟踪
使用StackTrace类可以得到当前执行栈的相关信息,通过接口GetFrame可以得到当前哪一层调用的相关信息:
public class TestClass : MonoBehaviour {
private void Start() {
Method1();
}
private void Method1() {
Method2();
}
private void Method2() {
var st = new System.Diagnostics.StackTrace(true);
var sf = st.GetFrame(0);
Debug.Log(sf.GetMethod().Name);
sf = st.GetFrame(1);
Debug.Log(sf.GetMethod().Name);
sf = st.GetFrame(2);
Debug.Log(sf.GetMethod().Name);
//Print:
//Method2
//Method1
//Start
}
}
但是之前提到,协程会在编译后转换为状态机,所以下面这个代码就得不到栈信息:
public class TestClass : MonoBehaviour {
private void Start() {
StartCoroutine(A());
}
private IEnumerator A() {
yield return null;
yield return B();
}
private IEnumerator B() {
yield return null;
Debug.Log("Hello");
}
}
打印:
Hello
UnityEngine.Debug:Log (object)
TestClass/<B>d__2:MoveNext () (Assets/TestClass.cs:14)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)
不过抖个机灵,在非yield语句中进行常规代码的调用或函数调用,可以正常拿到类名和代码行数:
1 public class TestClass : MonoBehaviour
2 {
3 private StringBuilder mStb = new StringBuilder(1024);
4
5 private void Start() {
6 StartCoroutine(A());
7 }
8 private IEnumerator A() {
9 StackTrace st = new StackTrace(true);
10 mStb.AppendLine(st.GetFrame(0).GetFileLineNumber().ToString());
11 yield return B();
12 }
13 private IEnumerator B() {
14 StackTrace st = new StackTrace(true);
15 mStb.AppendLine(st.GetFrame(0).GetFileLineNumber().ToString());
16 yield return C();
17 }
18 private IEnumerator C() {
19 StackTrace st = new StackTrace(true);
20 mStb.AppendLine(st.GetFrame(0).GetFileLineNumber().ToString());
21 yield return null;
22 UnityEngine.Debug.Log(mStb.ToString());
23 }
24 }
打印:
14
19
24
下面将基于这个思路,继续封装协程代码。
2.StackTrace封装
2.1 Begin/End 语句块
下一步,我们可以创建一个CoroutineHelper类和栈对象,保存每一步的栈跟踪信息:
public static class CoroutineHelper
{
private static StackTrace[] sStackTraceStack;
private static int sStackTraceStackNum; static CoroutineHelper()
{
sStackTraceStack = new StackTrace[64];
sStackTraceStackNum = 0;
}
public static void BeginStackTraceStabDot() {
sStackTraceStack[sStackTraceStackNum] = new StackTrace(true);
++sStackTraceStackNum;
}
public static void EndStackTraceStabDot() {
sStackTraceStack[sStackTraceStackNum-1] = null;
--sStackTraceStackNum; }
}
注意这里没有直接用C#自己的Stack,是因为无法逆序遍历,不方便输出栈日志。
但这样的话,每一步协程函数跳转都要用Begin、End语句包装又太丑。
private void Start() {
StartCoroutine(A());
}
private IEnumerator A() {
CoroutineHelper.BeginStackTraceStabDot();
yield return B();
CoroutineHelper.EndStackTraceStabDot();
}
2.2 使用扩展方法与using语法糖优化
实际上非yield语句,普通函数调用也是可以的,编译后不会被转换,可以用扩展方法优化下:
public static class CoroutineHelper
{
//加入了这个函数:
public static IEnumerator StackTrace(this IEnumerator enumerator)
{
BeginStackTraceStabDot();
return enumerator;
}
}
这样调用时就舒服多了,对原始代码的改动也最小:
private void Start() {
StartCoroutine(A());
}
private IEnumerator A() {
yield return B().StackTrace();
}
private IEnumerator B() {
yield return C().StackTrace();
}
但还要处理函数结束时调用Pop方法,这个可以使用using语法糖:
//加入该结构体
public struct CoroutineStabDotAutoDispose : IDisposable {
public void Dispose() {
CoroutineHelper.EndStackTraceStabDot();
}
}
public static class CoroutineHelper
{
//加入该函数
public static CoroutineStabDotAutoDispose StackTracePop() {
return new CoroutineStabDotAutoDispose();
}
}
最终调用时如下:
private void Start()
{
StartCoroutine(A());
}
private IEnumerator A()
{
using var _ = CoroutineHelper.StackTracePop(); yield return null;yield return B().StackTrace();
//...
}
private IEnumerator B()
{
using var _ = CoroutineHelper.StackTracePop(); yield return null;
yield return C().StackTrace();
yield return null;//... }
3.打印输出
通过StackTrace类以及语法糖处理,可以拿到完整栈信息后,还需要打印输出,
我们可以加入Unity编辑器下IDE链接的语法,这样打印日志直接具有超链接效果:
public static void PrintStackTrace()
{
var stb = new StringBuilder(4096);
stb.AppendLine(" --- Coroutine Helper StackTrace --- ");
for (int i = 0; i < sStackTraceStackNum; ++i)
{
var sf = sStackTraceStack[i].GetFrame(2);
stb.AppendFormat("- {0} (at <a href=\"{1}\" line=\"{2}\">{1}:{2}</a>\n", sf.GetMethod().Name, sf.GetFileName(), sf.GetFileLineNumber());
}
stb.AppendLine(" --- Coroutine Helper StackTrace --- "); UnityEngine.Debug.Log(stb.ToString());
}
最终效果如下:

扩展实现Unity协程的完整栈跟踪的更多相关文章
- Unity协程(Coroutine)原理深入剖析再续
Unity协程(Coroutine)原理深入剖析再续 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面已经介绍过对协程(Coroutine ...
- Unity 协程运行时的监控和优化
我是快乐的搬运工: http://gulu-dev.com/post/perf_assist/2016-12-20-unity-coroutine-optimizing#toc_0 --------- ...
- 聊一聊Unity协程背后的实现原理
Unity开发不可避免的要用到协程(Coroutine),协程同步代码做异步任务的特性使程序员摆脱了曾经异步操作加回调的编码方式,使代码逻辑更加连贯易读.然而在惊讶于协程的好用与神奇的同时,因为不清楚 ...
- Unity协程(Coroutine)管理类——TaskManager工具分享
博客分类: Unity3D插件学习,工具分享 源码分析 Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...
- Unity协程(Coroutine)原理深入剖析
Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 其实协程并没有那么复杂,网上很多地方都说是多 ...
- Unity协程(Coroutine)原理深入剖析(转载)
记得去年6月份刚开始实习的时候,当时要我写网络层的结构,用到了协程,当时有点懵,完全不知道Unity协程的执行机制是怎么样的,只是知道函数的返回值是IEnumerator类型,函数中使用yield r ...
- Lua的函数调用和协程中,栈的变化情况
Lua的函数调用和协程中,栈的变化情况 1. lua_call / lua_pcall 对于这两个函数,对栈底是没有影响的--调用的时候,参数会被从栈中移除,当函数返 回的时候,其返回值会从函数处 ...
- unity协程coroutine浅析
转载请标明出处:http://www.cnblogs.com/zblade/ 一.序言 在unity的游戏开发中,对于异步操作,有一个避免不了的操作: 协程,以前一直理解的懵懵懂懂,最近认真充电了一下 ...
- Unity协程Coroutine使用总结和一些坑
原文摘自 Unity协程Coroutine使用总结和一些坑 MonoBehavior关于协程提供了下面几个接口: 可以使用函数或者函数名字符串来启动一个协程,同时可以用函数,函数名字符串,和Corou ...
- 深入浅出!从语义角度分析隐藏在Unity协程背后的原理
Unity的协程使用起来比较方便,但是由于其封装和隐藏了太多细节,使其看起来比较神秘.比如协程是否是真正的异步执行?协程与线程到底是什么关系?本文将从语义角度来分析隐藏在协程背后的原理,并使用C++来 ...
随机推荐
- #费马小定理#JZOJ 4015 数列
题目 给出\(x_n=(ax_{n-1}^2+bx_{n-1}+c)\bmod m\) 给出\(x_0.a,b,c,n,m\),求\(x_n\) \(\text{Subtask 1:}n\leq 10 ...
- #威佐夫博弈#洛谷 2252 [SHOI2002]取石子游戏
题目 有两堆石子,数量任意,可以不同.游戏开始由两个人轮流取石子. 游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子: 二是可以在两堆中同时取走相同数量的石子.最后把石子全部取完 ...
- PWA 实践/应用(Google Workbox)
桌面端 PWA 应用: 移动端添加到桌面: 1 什么是 PWA PWA(Progressive Web App - 渐进式网页应用)是一种理念,由 Google Chrome 在 2015 年提出.P ...
- OpenHarmony—应用间HSP开发指导
应用间HSP用于不同应用间的代码.资源共享. 应用间HSP的宿主应用是一种特殊状态的应用,只能由一个HSP组成,不会独立运行在设备上,而是被普通应用模块的依赖项引用.当普通应用运行时,通过动态调用的 ...
- 1.NCC算法实现及其优化[基础实现篇]
NCC算法实现及其优化 本文将集中探讨一种实现相对简单,效果较好的模板匹配算法(NCC) \[R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x', ...
- 什么是慢SQL且如何查看慢SQL
什么是慢 SQL 且如何查看慢 SQL? 介绍 某个 SQL 执行时间超过指定时间时称为慢 SQL.我们可以查看慢 SQL,包括历史慢 SQL 以及当前慢 SQL. 查看历史慢 SQL 首先要设置 l ...
- 基于Canvas实现的简历编辑器
基于Canvas实现的简历编辑器 大概一个月前,我发现社区老是给我推荐Canvas相关的内容,比如很多 小游戏.流程图编辑器.图片编辑器 等等各种各样的项目,不知道是不是因为我某一天点击了相关内容触发 ...
- spring boot properties 编码问题[四]
情景 application.properties 中: server.port=8081 person.last-name=张三 person.age=18 person.birth=2017/12 ...
- HarmonyOS NEXT应用开发案例——列表编辑实现
介绍 本示例介绍用过使用ListItem组件属性swipeAction实现列表左滑编辑效果的功能. 该场景多用于待办事项管理.文件管理.备忘录的记录管理等. 效果图预览 使用说明: 点击添加按钮,选择 ...
- Apsara Stack 技术百科 | 如何「场景化」的企业上云
简介: 企业上云离不开数据和业务上云,如何在确保安全的前提下,低成本高效率的平滑上云,在云上又能真正解决哪些实际业务问题?混合云君今天给大家讲讲最经典的三个场景~ 随着"十四五"规 ...