我的应用程序中有一个IHostedService
。它启动了一个 Discord 聊天机器人和一个处理实时数据的交易机器人。一旦应用程序加载(IHostApplicationLifetime.ApplicationStarted
),它们都会启动。
Discord 聊天机器人正在一个单独的线程中启动,因为它必须在整个应用程序中都处于活动状态。它应该控制交易机器人。我的意思是用户可以发送 /startbot、/stopbot、/help 等命令。
交易机器人是应用程序的主要逻辑。它基本上连接到 Binance Web 套接字流并处理实时数据。
问题
- 当我这样做时
Ctrl + C
, OnStopping 被执行,但它永远不会到达 OnStopped 或停止应用程序,因为ITradeManager.Run
它正在运行一个无限循环。
注意:我没有使用 BackgroundService 的原因是因为这些 ITradeManager 和 IDiscordCommandHandler 应该在应用程序加载后启动(IHostApplicationLifetime.ApplicationStarted
)。
OnStarted has been called.
[2021-02-28 00:41:49 Information] ElonMuskBot.Discord.DiscordCommandHandler
DiscordCommandHandler is starting.
[2021-02-28 00:41:49 Information] ElonMuskBot.Discord.DiscordCommandHandler
[Discord.NET - Discord] Discord.Net v2.3.0-dev-20210121.1 (API v6)
[2021-02-28 00:41:49 Information] ElonMuskBot.Discord.DiscordCommandHandler
[Discord.NET - Gateway] Connecting
[2021-02-28 00:41:50 Information] ElonMuskBot.Discord.DiscordCommandHandler
[Discord.NET - Gateway] Connected
[2021-02-28 00:41:51 Information] ElonMuskBot.Discord.DiscordCommandHandler
[Discord.NET - Gateway] Ready
[2021-02-28 00:41:51 Information] ElonMuskBot.Core.TradeManagers.LiveTradeManager
Test
[2021-02-28 00:41:52 Information] ElonMuskBot.Core.TradeManagers.LiveTradeManager
Test
[2021-02-28 00:41:52 Information] ElonMuskBot.Core.TradeManagers.LiveTradeManager
Test
[2021-02-28 00:41:52 Information] ElonMuskBot.BotHostedService
OnStopping has been called.
[2021-02-28 00:41:53 Information] ElonMuskBot.Core.TradeManagers.LiveTradeManager
Test
[2021-02-28 00:41:53 Information] ElonMuskBot.Core.TradeManagers.LiveTradeManager
Test
[2021-02-28 00:41:54 Information] ElonMuskBot.Core.TradeManagers.LiveTradeManager
Test
[2021-02-28 00:41:54 Information] ElonMuskBot.Core.TradeManagers.LiveTradeManager
Test
- 我希望能够
ITradeManager.Run
从外部启动/停止,更具体地说是从BotControlModule:
public class BotControlModule : ModuleBase<SocketCommandContext>
{
[Command("start")]
public async Task StartAsync()
{
await ReplyAsync("Bot has started.");
}
[Command("forcebuy", RunMode = RunMode.Async)]
public async Task ForceBuy(string pair)
{
await ReplyAsync("Forcebuy executed");
}
}
片段
services.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig { LogLevel = LogSeverity.Info }));
services.AddSingleton(new CommandService(new CommandServiceConfig { LogLevel = LogSeverity.Info }));
services.AddSingleton<IDiscordCommandHandler, DiscordCommandHandler>();
services.AddSingleton<LiveTradeManager>();
services.AddSingleton<BacktestTradeManager>();
services.AddSingleton<ITradeManagerFactory, TradeManagerFactory>();
public class BotHostedService : IHostedService
{
private readonly ILogger<BotHostedService> _logger;
private readonly IHostApplicationLifetime _appLifetime;
private readonly IDiscordCommandHandler _handler;
private readonly ITradeManager _tradeManager;
public BotHostedService(
ILogger<BotHostedService> logger,
IHostApplicationLifetime appLifetime,
IDiscordCommandHandler commandHandler,
ITradeManagerFactory tradeManagerFactory,
IOptions<ExchangeOptions> options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_appLifetime = appLifetime;
_handler = commandHandler;
_tradeManager = tradeManagerFactory.GetTradeManager(options.Value.TradeManagerType);
}
public Task StartAsync(CancellationToken cancellationToken)
{
_appLifetime.ApplicationStarted.Register(OnStarted);
_appLifetime.ApplicationStopping.Register(OnStopping);
_appLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await _handler.StopAsync();
}
private void OnStarted()
{
_logger.LogInformation("OnStarted has been called.");
Task.Run(async () => await _handler.StartAsync());
// TODO: This one is still running after we close the app
_tradeManager.Run();
}
private void OnStopping()
{
_logger.LogInformation("OnStopping has been called.");
}
private void OnStopped()
{
_logger.LogInformation("OnStopped has been called.");
}
}
public class LiveTradeManager : ITradeManager
{
private readonly ILogger<LiveTradeManager> _logger;
private readonly IClient _client;
public LiveTradeManager(
ILogger<LiveTradeManager> logger, IOptions<ExchangeOptions> options, IClientFactory clientFactory)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_client = clientFactory.GetClient(options.Value.ClientType);
}
public void Run()
{
while (true)
{
_logger.LogInformation("Test");
Thread.Sleep(500);
}
}
}
public class DiscordCommandHandler : IDiscordCommandHandler
{
private readonly ILogger<DiscordCommandHandler> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IOptions<DiscordOptions> _discordOptions;
private readonly DiscordSocketClient _client;
private readonly CommandService _commands;
public DiscordCommandHandler(
ILogger<DiscordCommandHandler> logger,
IServiceProvider serviceProvider,
IOptions<DiscordOptions> discordOptions,
DiscordSocketClient client,
CommandService commands)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_serviceProvider = serviceProvider;
_discordOptions = discordOptions;
_client = client;
_commands = commands;
}
public async Task StartAsync()
{
_logger.LogInformation("DiscordCommandHandler is starting.");
_client.Ready += ReadyAsync;
_client.MessageReceived += HandleCommandAsync;
_client.Log += LogAsync;
_commands.CommandExecuted += CommandExecutedAsync;
_commands.Log += LogAsync;
await _commands.AddModulesAsync(Assembly.GetExecutingAssembly(), _serviceProvider).ConfigureAwait(false);
if (!string.IsNullOrEmpty(_discordOptions.Value.Token))
{
await _client.LoginAsync(TokenType.Bot, _discordOptions.Value.Token).ConfigureAwait(false);
await _client.StartAsync().ConfigureAwait(false);
}
}
public async Task StopAsync()
{
_logger.LogInformation("DiscordCommandHandler is stopping.");
await _client.SetStatusAsync(UserStatus.Offline).ConfigureAwait(false);
await _client.SetGameAsync(null).ConfigureAwait(false);
await _client.StopAsync().ConfigureAwait(false);
_client.Ready -= ReadyAsync;
_client.MessageReceived -= HandleCommandAsync;
_client.Log -= LogAsync;
_commands.CommandExecuted -= CommandExecutedAsync;
_commands.Log -= LogAsync;
}
...
}