Возможно, я знаю ответ на свой заданный вопрос: я использую внедрение зависимостей конструктора во всем приложении, которое представляет собой зацикленное консольное приложение C#, которое не завершается после каждого запроса.
Я подозреваю, что Из-за этого время жизни всех включенных объектов практически бесконечно. При попытке настроить время жизни во время регистрации он предупреждает, что временный объект не может быть реализован на одноэлементном объекте из-за зависимостей (которые вдохновили взглянуть на использование памяти и этот вопрос).
Это мой первый опыт консольное приложение, бот, который входит в систему поставщика услуг и ожидает сообщений. Я пришел из. NET Core Web API, который снова имеет зависимости повсюду, но я думаю, что ключевое различие здесь ниже всего моего кода - это сама платформа, которая обрабатывает каждый запрос индивидуально, а затем убивает запущенный поток.
Насколько я близок? Должен ли я отделить самого бота от базового консольного приложения, которое слушает поставщика услуг, и пытаться реплицировать платформу, которую предоставляет маршрутизация IIS / kestrel / MVC для разделения отдельных запросов?
Edit : Изначально я задумал этот вопрос скорее как принцип дизайна, передовой опыт или вопрос направления. Люди запросили воспроизводимый код, поэтому здесь мы go:
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();
}
}
}
Bot.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;
}
}
}
EventHandler.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);
}
}
}
SignalHandler.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. В моем коде нет механизма, позволяющего использовать эти ресурсы go, и я подозреваю, что предполагал, что Io C собирался выполнить magi c и освободить ресурсы.
Однако отправка сообщений на бот постоянно увеличивает количество объектов в соответствии с использованием памяти Visual Studio. Это отражается и на реальной памяти процесса.
Хотя, редактируя этот пост для утверждения, я думаю, что в конечном итоге я мог неправильно интерпретировать инструменты диагностики c Visual Studio. Похоже, что использование памяти приложением составляет около 36 МБ после 15 минут работы. Или он просто увеличивается так мало за раз, что это трудно увидеть.
Сравнивая снимки использования памяти, которые я сделал за 1 минуту и 17 минут, похоже, что был создан по 1 из каждого из вышеперечисленных объектов. Если я правильно это читаю, я полагаю, что это доказывает, что Io C не создает новые объекты (или они удаляются до того, как у меня появится возможность создать снимок.