0

我的应用程序中有一个IHostedService。它启动了一个 Discord 聊天机器人和一个处理实时数据的交易机器人。一旦应用程序加载(IHostApplicationLifetime.ApplicationStarted),它们都会启动。

Discord 聊天机器人正在一个单独的线程中启动,因为它必须在整个应用程序中都处于活动状态。它应该控制交易机器人。我的意思是用户可以发送 /startbot、/stopbot、/help 等命令。

交易机器人是应用程序的主要逻辑。它基本上连接到 Binance Web 套接字流并处理实时数据。

问题

  1. 当我这样做时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
  1. 我希望能够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;
    }

    ...
}
4

1 回答 1

1

当我执行 Ctrl + C 时,OnStopping 被执行,但它永远不会到达 OnStopped 或停止应用程序,因为 ITradeManager.Run 正在运行一个无限循环。

要取消代码,首先定义一个CancellationTokenSource. 将 传递CancellationToken到正在运行的代码 ( ITradeManager.Run),并在那里观察令牌(即,将令牌传递给另一个方法,如Task.Delay,或定期调用ThrowIfCancellationRequested)。然后,当您要求取消时(即ApplicationStopping),取消CancellationTokenSource.

旁注:BackgroundService为您执行此操作。我不确定你为什么不能使用它。

我希望能够从外部,更具体地说是从 BotControlModule 启动/停止/暂停 ITradeManager.Run

为此,您需要有一个同步原语,例如ManualResetEvent(或AsyncManualResetEvent)。让您的代码(即ITradeManager.Run)定期等待设置此事件。当暂停请求进来时,重置事件,当恢复请求进来时,设置事件。

于 2021-02-27T23:30:09.897 回答