Позвольте мне начать с указания, что запуск долгоживущих Task
внутри бота не будет очень масштабируемым решением. Как и веб-приложения, боты, как правило, масштабируются на нескольких серверах, а также должны быть терпимы к процессу или перезапуску сервера. Вы, вероятно, захотите использовать какую-то внешнюю распределенную систему таймера, которая гарантирует, что, независимо от времени жизни вашего бота, таймер будет сохраняться и в конечном итоге вызываться. Кроме того, это не очень хорошее использование машинных ресурсов. Если у вашего бота 100 или, надеюсь, 1000 пользователей, и вы постоянно создаете Task
с Task::Delay
, то вы будете испытывать немало накладных расходов в плане ресурсов. Обычно решение, подобное этому, состоит в том, чтобы иметь хранилище таймеров, которое обслуживается одним работником.
Хорошо, оставим в стороне это предупреждение, давайте просто поговорим о конкретных проблемах, с которыми вы столкнулись:
- Я не могу отменить эту задачу, если пользователь уже сбросил диалог вручную через диалоги.
Ну, вы могли бы ... вам просто нужно создать компаньона CancellationTokenSource
, передать его Token
Task.Delay
и ContinueWith
, а затем, если вы хотите отменить его, вызвать его Cancel
метод, который освобождает таймер задержки и гарантирует, что он никогда не вызывается.
Я не знаю, что именно EndConversation
находится в вашем примере кода, но вместо того, чтобы просто быть Task
, теперь нужно будет иметь структуру данных с Task
и CancellationToken
. Здесь может работать простой кортеж, иначе создайте себе новый класс.
- Состояния в Задании Задержки не обновляются. Например, если пользователь добавляет заметку в список, состояние в задаче задержки в конце диалога равно 0.
Да, вы видите устаревшее состояние, потому что закрываете исходную переменную dialogContext
продолжением. Технически вы не должны использовать что-то вроде DialogContext
или ITurnContext
вне текущего хода.
То, что вы пытаетесь сделать здесь, это то, что называется проактивным обменом сообщениями. Даже если ваша логика на самом деле не отправляет сообщение пользователю, применима та же концепция. Итак, что вы хотели бы сделать, это на самом деле захватить ConversationReference
вне замыкания для продолжения, а затем использовать это ConversationReference
внутри замыкания, чтобы продолжить разговор позже. Это будет выглядеть примерно так:
// Capture the conversation reference from the current turn's activity
var currentConversationReference = dialogContext.Context.Activity.GetConversationReference();
// Capture the adapter of the current turn (this is fugly, but kind of the best way to do it right now)
var botAdapter = dialogContext.Context.Adapter;
// Kick off your timer with the continuation closing over these variables for later use
Task.Delay(600000).ContinueWith(async (t) =>
{
await botAdapter.ContinueConversationAsync(
YourBotsApplicationId,
currentConversationReference,
(proactiveTurnContext, ct) => {
// Now you can load state from the proactive turn context which will be linked to the conversation reference provided
var xyzState = await GetXYZState(proactiveTurnContext);
// Example of sending a message through the proactive turn context
await proactiveTurnContext.SendActivityAsync("Hi, I just did a thing because I didn't hear from you for two hours.");
});
}