You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在CommandBuffer中调用托管函数引发域重载卡顿问题

问题描述

通过CommandBuffer.IssuePluginEventAndData调用托管函数,或从C++原生插件线程调用托管函数指针时,运行阶段一切正常,但停止游戏触发域重载时,编辑器卡在以下日志阶段,只能重启恢复:

Reloading assemblies for play mode.
Begin MonoManager ReloadAssembly

相关代码如下:

C# 委托与回调定义

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public unsafe delegate void PluginDelegate(int eventId, void* data);

[MonoPInvokeCallback(typeof(PluginDelegate))]
private static unsafe void MakeGraphicsCallback(int eventId, void* data)
{
    // 空实现或业务逻辑
}

委托存储与CommandBuffer调用

// 尝试通过静态字段防止委托被GC回收
private static PluginDelegate makeCallbackDelegate;
public void Start()
{
    makeCallbackDelegate = MakeGraphicsCallback;
    cmdBuffer.IssuePluginEventAndData(
                    Marshal.GetFunctionPointerForDelegate(makeCallbackDelegate),
                    0, IntPtr.Zero);
}

C++插件调用代码

typedef void (__stdcall *PluginCallback)(int32_t eventId, void *data);
auto func = static_cast<PluginCallback>((void*)funcPtr);
func((int32_t)eventIdValue, (void*)dataValue);

注:切换cdecl/stdcall调用约定并同步修改UnmanagedFunctionPointerAttribute后问题依旧;主线程直接调用无异常,仅非托管线程调用后触发域重载卡顿。

解决方案

1. 域重载前清理非托管侧的函数指针引用

Unity域重载时,原托管程序集被卸载,旧函数指针失效。需在域重载前通知C++插件清除所有托管函数指针引用,停止相关线程调用:

  • C#侧订阅PlayMode状态变更事件,触发清理逻辑:
private void OnEnable()
{
    EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}

private void OnDisable()
{
    EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}

private void OnPlayModeStateChanged(PlayModeStateChange state)
{
    if (state == PlayModeStateChange.ExitingPlayMode)
    {
        // 调用C++插件清理函数,停止持有托管指针的线程/任务
        NativePlugin.ClearAllManagedCallbacks();
        // 等待所有非托管线程完成当前调用
        NativePlugin.WaitForCallbacksComplete();
    }
}
  • C++侧实现清理逻辑:
#include <atomic>
#include <vector>
#include <thread>
#include <condition_variable>

std::atomic<bool> s_ShouldStopCallbacks = false;
std::vector<PluginCallback> s_ActiveCallbacks;
std::condition_variable s_Cv;
std::mutex s_Mtx;
int s_ActiveCallCount = 0;

extern "C" void __declspec(dllexport) ClearAllManagedCallbacks()
{
    s_ShouldStopCallbacks = true;
    std::lock_guard<std::mutex> lock(s_Mtx);
    s_ActiveCallbacks.clear();
}

extern "C" void __declspec(dllexport) WaitForCallbacksComplete()
{
    std::unique_lock<std::mutex> lock(s_Mtx);
    s_Cv.wait(lock, []{ return s_ActiveCallCount == 0; });
}

void InvokeManagedCallback(int32_t eventId, void* data)
{
    if (s_ShouldStopCallbacks) return;
    
    std::lock_guard<std::mutex> lock(s_Mtx);
    s_ActiveCallCount++;
    for (auto& cb : s_ActiveCallbacks)
    {
        cb(eventId, data);
    }
    s_ActiveCallCount--;
    s_Cv.notify_one();
}

2. 避免静态委托的跨域重载残留

域重载时静态字段会被重新初始化,旧委托实例可能仍被非托管侧持有。可在每次进入PlayMode时重新注册委托:

public void Start()
{
    // 每次进入PlayMode重新绑定委托
    makeCallbackDelegate = MakeGraphicsCallback;
    var funcPtr = Marshal.GetFunctionPointerForDelegate(makeCallbackDelegate);
    NativePlugin.RegisterManagedCallback(funcPtr);
    cmdBuffer.IssuePluginEventAndData(funcPtr, 0, IntPtr.Zero);
}

3. 托管回调内增加域重载状态检查

在托管回调中先判断是否处于域重载阶段,避免无效执行:

public static class DomainReloadStatus
{
    public static volatile bool IsDomainReloading = false;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void Init()
    {
        IsDomainReloading = false;
        EditorApplication.playModeStateChanged += state =>
        {
            if (state == PlayModeStateChange.ExitingPlayMode)
            {
                IsDomainReloading = true;
            }
        };
    }
}

[MonoPInvokeCallback(typeof(PluginDelegate))]
private static unsafe void MakeGraphicsCallback(int eventId, void* data)
{
    if (DomainReloadStatus.IsDomainReloading)
    {
        return;
    }
    // 原业务逻辑
}

4. 改用Unity Job System替代原生线程(可选)

若业务允许,使用Unity Job System处理异步任务,它会自动处理域重载时的任务清理,避免直接使用原生线程调用托管函数。

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 08:01:27