C#/Xamarin/Android中如何获取所有托管线程的堆栈跟踪?
Xamarin.Android ANR排查:托管线程全量堆栈抓取方案
问题核心原因
Process.GetCurrentProcess().Threads在Xamarin.Android的Mono运行时下未做托管层适配,返回的是内核原生线程的非托管封装对象,没有和托管线程上下文做映射,因此遍历后拿到的条目全为null,无法直接用于托管栈抓取。
现有面向.NET Framework/.NET Core桌面、服务端的线程栈抓取方案,大多依赖Windows平台的调试API或是.NET Core的运行时事件源,在Mono运行时的Android环境下无法直接运行。
可落地实现方案
Xamarin.Android基于Mono运行时,可通过反射调用Mono内部接口实现全托管线程的堆栈抓取,无需引入第三方组件,Release环境下也可正常运行:
- 前置配置
打包前在项目属性中把调试符号设置为Portable PDB并选择嵌入程序集,否则抓取到的堆栈会缺失方法名、行号信息,无法定位问题。 - 核心实现代码
using System.Reflection; using System.Text; public static string DumpAllManagedThreadStacks() { var logBuilder = new StringBuilder(); var monoRuntimeType = Type.GetType("Mono.Runtime"); var getStackMethod = monoRuntimeType?.GetMethod("GetStackTrace", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Thread), typeof(bool) }, null); if (getStackMethod == null) { logBuilder.AppendLine("当前Mono版本不支持托管栈抓取"); return logBuilder.ToString(); } foreach (var thread in GetAliveManagedThreads()) { try { logBuilder.AppendLine($"=== ThreadId:{thread.ManagedThreadId} Name:{thread.Name ?? "unnamed"} State:{thread.ThreadState} ==="); var stackContent = getStackMethod.Invoke(null, new object[] { thread, true }) as string; logBuilder.AppendLine(stackContent); logBuilder.AppendLine(); } catch (Exception ex) { logBuilder.AppendLine($"Dump thread {thread.ManagedThreadId} failed: {ex.Message}"); } } return logBuilder.ToString(); } private static List<Thread> GetAliveManagedThreads() { var threads = new List<Thread>(); var threadType = typeof(Thread); var threadHashField = threadType.GetField("thread_hash", BindingFlags.Static | BindingFlags.NonPublic); if (threadHashField?.GetValue(null) is not IEnumerable threadHash) return threads; foreach (var entry in threadHash) { var valueField = entry.GetType().GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); if (valueField?.GetValue(entry) is Thread thread) { threads.Add(thread); } } return threads; }
- 注意事项
- 上述反射逻辑适配Mono 6.0及以上版本,覆盖2019年之后发布的所有Xamarin.Android正式版本
- 栈抓取逻辑必须放在ANR看门狗的独立工作线程执行,不能依赖已经阻塞的主线程调度,否则ANR触发时逻辑无法正常运行
- 抓取到的堆栈直接写入应用私有目录的独立日志文件,不要通过logcat输出,规避故障时段logcat丢日志的问题
扩展定位能力
如果需要同时覆盖Java层、Native层阻塞导致的ANR,可在托管栈抓取完成后调用Debug.NativeStackTraces()获取全量原生线程堆栈,通过线程ID和托管栈做映射,即可覆盖全链路的阻塞定位需求。
内容的提问来源于stack exchange,提问作者Ken K
相关产品推荐
相关产品推荐

