【Unity】修复Roslyn并发编译导致Editor崩溃
问题
前些日子,在使用Roslyn时,发现unity屡屡崩溃。修复Roslyn多线程编译在Unity中产生崩溃的bug
报错日志
0x00007FF827E1AB6A (KERNELBASE) RaiseException
0x00007FFF69CB0C1C (mono-2.0-bdwgc) [X\mono\mono\utils\mono-log-common.c:143] mono_log_write_logfile
0x00007FFF69C9C822 (mono-2.0-bdwgc) [X\mono\mono\eglib\goutput.c:172] monoeg_g_logv_nofree
0x00007FFF69C9C888 (mono-2.0-bdwgc) [X\mono\mono\eglib\goutput.c:187] monoeg_g_log
0x00007FFF69D8C1E7 (mono-2.0-bdwgc) [X\mono\mono\metadata\image.c:1632] mono_image_storage_tryaddref
0x00007FFF69D8C4B8 (mono-2.0-bdwgc) [X\mono\mono\metadata\image.c:1697] mono_image_storage_open
0x00007FFF69D8C8BB (mono-2.0-bdwgc) [X\mono\mono\metadata\image.c:1777] do_mono_image_open
0x00007FFF69D4BBFB (mono-2.0-bdwgc) [X\mono\mono\metadata\icall.c:6209] ves_icall_System_Reflection_Assembly_InternalGetAssemblyName
0x00007FFF69D652E0 (mono-2.0-bdwgc) [X\mono\mono\metadata\icall-def.h:608] ves_icall_System_Reflection_Assembly_InternalGetAssemblyName_raw
0x0000021F5FB8E2E6 (Mono JIT Code) (wrapper managed-to-native) System.Reflection.Assembly:InternalGetAssemblyName (string,Mono.MonoAssemblyName&,string&)
0x0000021F5FB8E00B (Mono JIT Code) System.Reflection.AssemblyName:GetAssemblyName (string)
0x0000021F5FB8DD73 (Mono JIT Code) Microsoft.CodeAnalysis.MonoGlobalAssemblyCache:CreateAssemblyNameFromFile (string)
0x0000021F5FB84AE3 (Mono JIT Code) Microsoft.CodeAnalysis.MonoGlobalAssemblyCache:ResolvePartialName (string,string&,System.Collections.Immutable.ImmutableArray`1<System.Reflection.ProcessorArchitecture>,System.Globalization.CultureInfo)
0x0000021F5FB83AB2 (Mono JIT Code) Microsoft.CodeAnalysis.Scripting.Hosting.GacFileResolver:Resolve (string)
0x0000021F5FB7C03B (Mono JIT Code) Microsoft.CodeAnalysis.Scripting.Hosting.RuntimeMetadataReferenceResolver:ResolveMissingAssembly (Microsoft.CodeAnalysis.MetadataReference,Microsoft.CodeAnalysis.AssemblyIdentity)
0x0000021F5FB7BE1E (Mono JIT Code) Microsoft.CodeAnalysis.Scripting.ScriptMetadataResolver:ResolveMissingAssembly (Microsoft.CodeAnalysis.MetadataReference,Microsoft.CodeAnalysis.AssemblyIdentity)
0x0000021F5FB78E44 (Mono JIT Code) Microsoft.CodeAnalysis.CommonReferenceManager`2<TCompilation_REF, TAssemblySymbol_REF>:TryResolveMissingReference
经过阅读日志得知,问题出在:
0x0000021F5FB8E2E6 (Mono JIT Code) (wrapper managed-to-native) System.Reflection.Assembly:InternalGetAssemblyName (string,Mono.MonoAssemblyName&,string&)
之后就是进入了Mono内核的处理程序集环节,但就是这一步出现了问题。
Mono处理这块内容时的源码,对锁的处理出现了bug(这一点在新版mono中已修复,但在我测试环境Unity 2022.61f中仍未修复。)
解决方案
那么摆在眼前的路有两个
-
从Roslyn入手
-
从Mono入手
从Mono入手的话,可以找到Mono关于此修复的Commit,把image.c的代码更新一下重新构建dll即可。
本文的重头戏在于从Roslyn入手
流程
-
配合日志可以发现,这个问题在于Roslyn尝试寻找程序集。查询Roslyn官方文档可知,这主要通过
MetadataResolver来实现。 -
查询Roslyn官方文档,可知,当我们调用
CSharpScript.Create尝试编译时,我们可以为其指定一个ScriptOptions,其正好可以通过ScriptOptions.WithMetadataResolver来赋予属于自己的MetadataResolver! -
至此的难点在于怎么修复> MetadataResolver。 关注其源码可知
class ScriptMetadataResolver : MetadataReferenceResolver, IEquatable<LockedMetadataResolver>继承了MetadataReferenceResolver抽象类……但是ScriptMetadataResolver是sealed的,这个类没法继承,想实现里面的方法更是天方夜谭,他调用了internal的方法…… -
难道我们只能直接修改MetadataResolver,或者新建一个类实现方法,重新编译一份Roslyn了吗……?这当然可以,但是不够优雅!我们注意到,抽象类的方法都是public的,这个抽象类也是public的。我们没法直接实现这个抽象类的方法,那么答案呼之欲出:装饰模式。我们新建一个类作为wrapper,实现里面的方法,在关键的方法加上自己的锁即可!其他方法直接调用原本的官方默认实现。
源码
因为采用了装饰模式,代码不长,使用时只需在ScriptOptions.WithMetadataResolver添加上LockedMetadataResolver即可。
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Microsoft.CodeAnalysis.Scripting
{
class LockedMetadataResolver : MetadataReferenceResolver, IEquatable<LockedMetadataResolver>
{
private static readonly object _lockObject = new ();
private readonly ScriptMetadataResolver _resolver;
public LockedMetadataResolver(ScriptMetadataResolver innerResolver)
{
_resolver = innerResolver;
}
# pragma warning disable CS8632
public ScriptMetadataResolver WithSearchPaths(params string[] searchPaths)
=> _resolver.WithSearchPaths(searchPaths);
public ScriptMetadataResolver WithSearchPaths(IEnumerable<string> searchPaths)
=> _resolver.WithSearchPaths(searchPaths);
public ScriptMetadataResolver WithSearchPaths(ImmutableArray<string> searchPaths)
=> _resolver.WithSearchPaths(searchPaths);
public ScriptMetadataResolver WithBaseDirectory(string? baseDirectory)
=> _resolver.WithBaseDirectory(baseDirectory);
public override PortableExecutableReference? ResolveMissingAssembly(MetadataReference definition,
AssemblyIdentity referenceIdentity)
{
lock(_lockObject)
{
return _resolver.ResolveMissingAssembly(definition, referenceIdentity);
}
}
#nullable enable
public bool Equals(LockedMetadataResolver? other) => _resolver.Equals(other);
public override bool Equals(object? other) => Equals(other as LockedMetadataResolver);
public override int GetHashCode() => _resolver.GetHashCode();
#pragma warning disable CS8765
public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference,
string baseFilePath, MetadataReferenceProperties properties) =>
_resolver.ResolveReference(reference, baseFilePath, properties);
public override bool ResolveMissingAssemblies => _resolver.ResolveMissingAssemblies;
#pragma warning restore CS8765
}
}
小结
本文算是对解决unity该bug的小结……这个bug困扰了我两天,期间尝试过了各种升降版本的办法……没想到是官方的bug, 希望早日修复。
日后我还会分享关于unity学习过程中的更多知识,欢迎关注。

浙公网安备 33010602011771号