Позвольте мне начать с указания на то, что запуск долгоживущих 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.");
});
}
person
Drew Marsh
schedule
13.01.2019