垃圾收集发生得太早

Garbage Collection taking place too soon

我正在研究开发跨平台代码,在对 Windows 和 Linux 中的退出代码进行了一些研究后,我拼凑了下面的 class 来处理让我的控制台应用程序保持活动状态。但是,关闭后我收到错误:

Process terminated. A callback was made on a garbage collected delegate of type 'Bot!Bot.Extensions.Environment.SignalHandler+SetConsoleCtrlEventHandler::Invoke'.

internal interface ISignalHandler
{
    void Set();
    void Wait();
    void Exit();
}

internal class SignalHandler : ISignalHandler
{
    private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false);
    private readonly SetConsoleCtrlHandler _setConsoleCtrlHandler;
    private bool _disposed;

    public SignalHandler()
    {
        if (!NativeLibrary.TryLoad("Kernel32", typeof(Library).Assembly, null, out var kernel)) return;
        if (NativeLibrary.TryGetExport(kernel, "SetConsoleCtrlHandler", out var handler))
            _setConsoleCtrlHandler =
                (SetConsoleCtrlHandler) Marshal.GetDelegateForFunctionPointer(handler,
                    typeof(SetConsoleCtrlHandler));
    }

    public void Set()
    {
        if (_setConsoleCtrlHandler == null) Task.Factory.StartNew(UnixSignalHandler);
        else _setConsoleCtrlHandler(WindowsSignalHandler, true);
    }

    public void Wait()
    {
        _resetEvent.WaitOne();
    }

    public void Exit()
    {
        _resetEvent.Set();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void UnixSignalHandler()
    {
        UnixSignal[] signals =
        {
            new UnixSignal(Signum.SIGHUP),
            new UnixSignal(Signum.SIGINT),
            new UnixSignal(Signum.SIGQUIT),
            new UnixSignal(Signum.SIGABRT),
            new UnixSignal(Signum.SIGTERM)
        };

        UnixSignal.WaitAny(signals);
        Exit();
    }

    private bool WindowsSignalHandler(WindowsCtrlType signal)
    {
        switch (signal)
        {
            case WindowsCtrlType.CtrlCEvent:
            case WindowsCtrlType.CtrlBreakEvent:
            case WindowsCtrlType.CtrlCloseEvent:
            case WindowsCtrlType.CtrlLogoffEvent:
            case WindowsCtrlType.CtrlShutdownEvent:
                Exit();
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(signal), signal, null);
        }

        return true;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing) _resetEvent.Dispose();

        _disposed = true;
    }

    private delegate bool SetConsoleCtrlHandler(SetConsoleCtrlEventHandler handlerRoutine, bool add);

    private delegate bool SetConsoleCtrlEventHandler(WindowsCtrlType sig);

    private enum WindowsCtrlType
    {
        CtrlCEvent = 0,
        CtrlBreakEvent = 1,
        CtrlCloseEvent = 2,
        CtrlLogoffEvent = 5,
        CtrlShutdownEvent = 6
    }
}

据我所知,收集 _setConsoleCtrlHandler 的时间过早,但我无法确定如何防止这种情况发生。即使在分配后不久调用 GC.KeepAlive(_setConsoleCtrlHandler),它仍然会产生错误。

您需要为 WindowsSignalHandler 创建一个 class-scoped 变量:

private readonly SetConsoleCtrlEventHandler
    _windowsSignalHandler = WindowsSignalHandler;

然后,将其传递给您的方法调用:

_setConsoleCtrlHandler(_windowsSignalHandler, true);

这将确保您的回调引用不会被收集,因为您在对象中保留了对它的引用。