垃圾收集发生得太早
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);
这将确保您的回调引用不会被收集,因为您在对象中保留了对它的引用。
我正在研究开发跨平台代码,在对 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);
这将确保您的回调引用不会被收集,因为您在对象中保留了对它的引用。