我可能知道我发布的问题的答案:我在整个应用程序中使用构造函数依赖注入,这是一个循环的 C# 控制台应用程序,在每次请求后都不会退出。
因此,我怀疑所有包含对象的寿命基本上是无限的。当尝试在注册时调整生命周期时,它警告说由于依赖关系而无法在单例对象上实现瞬态对象(这启发了查看内存利用率和这个问题)。
这是我的第一个基础控制台应用程序,一个机器人,它登录到服务提供商并等待消息。我来自 .NET Core Web API,它再次具有依赖关系,但我认为这里的关键区别在于我的所有代码下方是平台本身,它单独处理每个请求然后终止运行的线程。
我离我有多近?我是否必须将机器人本身与侦听服务提供商的基本控制台应用程序分开,并尝试复制 IIS/kestrel/MVC 路由提供的平台以分离各个请求?
编辑:最初我打算将这个问题更多地作为设计原则、最佳实践或询问方向。人们要求可重现的代码,所以我们开始:
namespace BotLesson
{
internal class Program
{
private static readonly Container Container;
static Program()
{
Container = new Container();
}
private static void Main(string[] args)
{
var config = new Configuration(args);
Container.AddConfiguration(args);
Container.AddLogging(config);
Container.Register<ITelegramBotClient>(() => new TelegramBotClient(config["TelegramToken"])
{
Timeout = TimeSpan.FromSeconds(30)
});
Container.Register<IBot, Bot>();
Container.Register<ISignalHandler, SignalHandler>();
Container.Register<IEventHandler, EventHandler>();
Container.Register<IEvent, MessageEvent>();
Container.Verify();
Container.GetInstance<IBot>().Process();
Container?.Dispose();
}
}
}
机器人.cs
namespace BotLesson
{
internal class Bot : IBot
{
private readonly ITelegramBotClient _client;
private readonly ISignalHandler _signalHandler;
private bool _disposed;
public Bot(ITelegramBotClient client, IEventHandler handler, ISignalHandler signalHandler)
{
_signalHandler = signalHandler;
_client = client;
_client.OnCallbackQuery += handler.OnCallbackQuery;
_client.OnInlineQuery += handler.OnInlineQuery;
_client.OnInlineResultChosen += handler.OnInlineResultChosen;
_client.OnMessage += handler.OnMessage;
_client.OnMessageEdited += handler.OnMessageEdited;
_client.OnReceiveError += (sender, args) => Log.Error(args.ApiRequestException.Message, args.ApiRequestException);
_client.OnReceiveGeneralError += (sender, args) => Log.Error(args.Exception.Message, args.Exception);
_client.OnUpdate += handler.OnUpdate;
}
public void Process()
{
_signalHandler.Set();
_client.StartReceiving();
Log.Information("Application running");
_signalHandler.Wait();
Log.Information("Application shutting down");
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing) _client.StopReceiving();
_disposed = true;
}
}
}
事件处理程序.cs
namespace BotLesson
{
internal class EventHandler : IEventHandler
{
public void OnCallbackQuery(object? sender, CallbackQueryEventArgs e)
{
Log.Debug("CallbackQueryEventArgs: {e}", e);
}
public void OnInlineQuery(object? sender, InlineQueryEventArgs e)
{
Log.Debug("InlineQueryEventArgs: {e}", e);
}
public void OnInlineResultChosen(object? sender, ChosenInlineResultEventArgs e)
{
Log.Debug("ChosenInlineResultEventArgs: {e}", e);
}
public void OnMessage(object? sender, MessageEventArgs e)
{
Log.Debug("MessageEventArgs: {e}", e);
}
public void OnMessageEdited(object? sender, MessageEventArgs e)
{
Log.Debug("MessageEventArgs: {e}", e);
}
public void OnReceiveError(object? sender, ReceiveErrorEventArgs e)
{
Log.Error(e.ApiRequestException, e.ApiRequestException.Message);
}
public void OnReceiveGeneralError(object? sender, ReceiveGeneralErrorEventArgs e)
{
Log.Error(e.Exception, e.Exception.Message);
}
public void OnUpdate(object? sender, UpdateEventArgs e)
{
Log.Debug("UpdateEventArgs: {e}", e);
}
}
}
信号处理器.cs
这与我的问题没有直接关系,但它使应用程序处于等待模式,而第三方库正在侦听消息。
namespace BotLesson
{
internal class SignalHandler : ISignalHandler
{
private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false);
private readonly SetConsoleCtrlHandler? _setConsoleCtrlHandler;
public SignalHandler()
{
if (!NativeLibrary.TryLoad("Kernel32", typeof(Library).Assembly, null, out var kernel)) return;
if (NativeLibrary.TryGetExport(kernel, "SetConsoleCtrlHandler", out var intPtr))
_setConsoleCtrlHandler = (SetConsoleCtrlHandler) Marshal.GetDelegateForFunctionPointer(intPtr,
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();
}
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;
}
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
}
}
}
我最初的观点是基于我对 SimpleInject 所做的一些假设——或者更具体地说,是我使用 SimpleInject 的方式。
应用程序保持运行,等待 SignalHandler._resetEvent。同时,消息通过 Bot.cs 构造函数上的任何处理程序进入。
所以我的想法/理论是 Main 启动 Bot.Process,它直接依赖于 ITelegramClient 和 IEventHandler。在我的代码中,没有一种机制可以让这些资源消失,我怀疑我假设 IoC 将执行魔法并释放资源。
但是,根据 Visual Studio 内存使用情况,向机器人发送消息会不断增加对象的数量。这也反映在实际的进程内存中。
不过,在编辑这篇文章以供批准时,我想我最终可能误解了 Visual Studio 的诊断工具。运行 15 分钟后,应用程序的内存利用率似乎保持在 36 MB 左右。或者它只是一次增加很少,以至于很难看到。
比较我在 1 分钟和 17 分钟时拍摄的内存使用快照,似乎上面创建的每个对象都有 1 个。如果我正确地阅读了这篇文章,我想这证明 IoC 没有创建新对象(或者它们在我有机会创建快照之前就被处置了。