0

Есть телеграмм-бот, у него есть команда, которая выполняется бесконечно, пока не придет апдейт с командой "/stop".

public override async void Execute(String command, Message message, ITelegramBotClient client, State state)
{
if (message.Text == "/stop")
    StopLoop();

if(message.Text == "/start")
    StartLoop(message,client);

}

private CancellationTokenSource _cts; private async Task RunLoopAsync(CancellationToken token,Message message, ITelegramBotClient client ) { try { while (true) { await client.SendTextMessageAsync(message.Chat.Id, "send", cancellationToken: token); await Task.Delay(10000, token); } } catch (OperationCanceledException) { } } private async void StartLoop(Message message, ITelegramBotClient client) { if (_cts != null) return; try { using (_cts = new CancellationTokenSource()) { await RunLoopAsync(_cts.Token,message,client); } } catch (Exception ex) { // ... ex.Message } _cts = null; }

private void StopLoop() { _cts?.Cancel(); }

Для остановки цикла, я взял пример отсюда: пример

Но это не помогает. Что посоветуете сделать, чтобы можно было остановить этот цикл?

Alex
  • 33
  • Код выглядит нормально. Используйте отладку и логирование. Либо вы не всё показали. – aepot Feb 12 '23 at 19:23
  • @aepot Это весь код, который есть в этой команде. В if (message.Text == "/stop") StopLoop(); код доходит, после чего оно присылает сообщение :"end"(я добавил туда CW, чтобы это увидеть + откладка). Меня смутило только то, что _cts.IsCancellationRequested остается false. – Alex Feb 12 '23 at 19:29
  • Ещё учтите, что этот код может обслуживать только одного клиента. Поле _cts не может быть использовано многократно. Вам понадобится что-то вроде словаря, который в качестве ключа использует id пользователя, а в качестве значения cts. – aepot Feb 12 '23 at 19:32
  • @aepot У меня уже реализован конечный автомат для других нужд. И тут, я так понял, его тоже нужно использовать? – Alex Feb 12 '23 at 19:36
  • Ну да, здесь что-то типа ConcurrentDictionary для хранения cts подойдёт. Но для одного единственного пользователя то что вы показали,должно работать нормально. – aepot Feb 12 '23 at 19:38
  • @aepot Пожалуй, тогда это и есть ответ для меня. Напишите ваш ответ, чтобы я отметил. Благодарю вас. – Alex Feb 12 '23 at 19:39
  • Лучше вы сами, когда разберётесь. Я не знаю, что именно писать, это были лишь догадки. – aepot Feb 12 '23 at 19:39

1 Answers1

0

Спасибо @aepot за догадки, ибо они помогли. Для успешного окончания цикла, у меня в коде нужно добавить конечный автомат:

public override async void Execute(String command, Message message, ITelegramBotClient client, State state)
{
    state.ActionCommand = "/start";
    var searchState = await _context.SearchState.FindAsync((e => e.UserId == message.Chat.Id))
        .Result.FirstOrDefaultAsync()??new SearchState(){_cts = new CancellationTokenSource()};
    _cts = searchState._cts;
if (message.Text == "/stop")
{
    searchState.IsWork = "false";
    await _stateMachine.DeleteState(message.Chat.Id);
    await _context.SearchState.ReplaceOneAsync((f=>f.UserId == message.Chat.Id),searchState);
    await client.SendTextMessageAsync(message.Chat.Id, "end");
    StopLoop();
}

if (message.Text == "/start")
{
    searchState.UserId = message.Chat.Id;
    searchState.IsWork = "true";
    await _context.SearchState.InsertOneAsync(searchState);
    StartLoop(message, client);

}

}

private CancellationTokenSource _cts; private async Task RunLoopAsync(CancellationToken token,Message message, ITelegramBotClient client ) { try { while (true) { var find = await _context.SearchState.FindAsync((e => e.UserId == message.Chat.Id)).Result.FirstOrDefaultAsync()??new SearchState(); if (find.IsWork == "false") { await _context.SearchState.DeleteOneAsync(f => f.UserId == message.Chat.Id); return; }

        await _parserWorker.Work(message, client, message.Chat.Id);
        await Task.Delay(120000, token); 
    }
}
catch (OperationCanceledException)
{ } 

} private async void StartLoop(Message message, ITelegramBotClient client) {

try
{
    using (_cts = new CancellationTokenSource())
    {
        await RunLoopAsync(_cts.Token,message,client);
    }
}
catch (Exception ex)
{
    // ... ex.Message
}
_cts = null;

}

private void StopLoop() { _cts?.Cancel(); }

Alex
  • 33
  • _cts тоже нужно унести в state. Подумайте еще хорошенько. Что будет, если у одного юзера уже всё выполняется, в другой выполнил эту же команду – aepot Feb 12 '23 at 20:17
  • @aepot Посмотрел, что будет. Если не добавить в state _cts, то бот перестанет отвечать. – Alex Feb 12 '23 at 21:09
  • Я ее вижу ваш код целиком и не имею времени долго в нем разбираться. Вам виднее, как лучше сделать. Я лишь указал на проблему. – aepot Feb 12 '23 at 21:55