垃圾收集时间过早

问题描述

我正在研究开发跨平台代码,并在对Windows和Linux中的退出代码进行了一些研究之后,将下面的类拼凑在一起,以使控制台应用程序保持活动状态。但是,关闭后会收到错误消息:

进程终止。对类型为“ 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创建一个类范围的变量:

private readonly SetConsoleCtrlEventHandler
    _windowsSignalHandler = WindowsSignalHandler;

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

_setConsoleCtrlHandler(_windowsSignalHandler,true);

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