【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入手

流程

  1. 配合日志可以发现,这个问题在于Roslyn尝试寻找程序集。查询Roslyn官方文档可知,这主要通过MetadataResolver来实现。

  2. 查询Roslyn官方文档,可知,当我们调用CSharpScript.Create尝试编译时,我们可以为其指定一个ScriptOptions,其正好可以通过ScriptOptions.WithMetadataResolver来赋予属于自己的MetadataResolver

  3. 至此的难点在于怎么修复> MetadataResolver。 关注其源码可知class ScriptMetadataResolver : MetadataReferenceResolver, IEquatable<LockedMetadataResolver>继承了MetadataReferenceResolver抽象类……但是ScriptMetadataResolver是sealed的,这个类没法继承,想实现里面的方法更是天方夜谭,他调用了internal的方法……

  4. 难道我们只能直接修改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学习过程中的更多知识,欢迎关注。

posted @ 2025-06-13 00:56  DLS童真  阅读(59)  评论(0)    收藏  举报