Расписание и состояния задач Microsoft BotFramework v4

Я использую botframework v4 от Microsoft для создания бота. Я реализовал задачу с задержкой, чтобы проверить, не ответил ли пользователь за последние 2 часа. Если истекает 2-часовой тайм-аут, функция выполнит какое-либо действие и сбросит состояние разговора. Это работает нормально, но есть 2 проблемы:

  1. Я не могу отменить эту задачу, если пользователь уже сбросил разговор вручную поверх диалогов.
  2. Состояния в задаче задержки не обновляются. Например, если пользователь добавляет заметку в список, состояние внутри задачи задержки равно 0 в конце разговора.

Мой код задачи задержки:

 EndConversation = Task.Delay(600000).ContinueWith(async (t) =>
            {
                bool wordGenerated = false;
                xyzState = await GetXYZState(dialogContext);

                if (xyzState.ListCount > 0)
                {
                    //retry 4 times sending the word document
                    for (int i = 0; i < 4; i++)
                    {
                        if (await GenerateWordDocument.CreateDoc(dc, _xyzAccessor, _xyzAccessor2))
                        {
                            wordGenerated = true;
                            break;
                        }
                    }
                }...

person theface    schedule 13.01.2019    source источник


Ответы (1)


Позвольте мне начать с указания на то, что запуск долгоживущих Task внутри бота не будет очень масштабируемым решением. Как и веб-приложения, боты, как правило, масштабируются на несколько серверов, а также должны быть устойчивы к перезапускам процессов или серверов. Скорее всего, вы захотите использовать какую-то внешнюю распределенную систему таймеров, которая гарантирует, что, независимо от времени жизни вашего бота, таймер будет сохраняться и в конечном итоге вызываться. Кроме того, это также не очень эффективное использование машинных ресурсов. Если у вашего бота 100 или, надеюсь, 1000 пользователей, и вы постоянно создаете Task с Task::Delay, вы будете нести довольно много накладных расходов с точки зрения ресурсов. Обычно такое решение состоит в том, чтобы иметь хранилище таймеров, которое обслуживает один работник.

Хорошо, это предупреждение в сторону, давайте просто поговорим о конкретных проблемах, с которыми вы столкнулись:

  1. Я не могу отменить эту задачу, если пользователь уже сбросил разговор вручную поверх диалогов.

Ну, вы могли бы... вам просто нужно создать компаньона CancellationTokenSource, передать его Token Task.Delay и ContinueWith, а затем, если вы хотите отменить его, вызвать его метод Cancel, который сбросит таймер задержки и гарантирует, что он никогда не сработает. вызывается.

Я не знаю, что такое EndConversation в вашем примере кода, но вместо того, чтобы быть просто Task, теперь это должна быть структура данных, в которой есть Task и CancellationToken. Здесь может работать простой кортеж, в противном случае создайте себе новый класс.

  1. Состояния в задаче задержки не обновляются. Например, если пользователь добавляет заметку в список, состояние внутри задачи задержки равно 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
comment
Спасибо за ваш ответ. Вы правы, я хочу отправить проактивное сообщение и отправить текстовый документ, когда пользователь не взаимодействовал с ботом в течение 6 часов. Разговор должен быть сброшен. У вас есть идея, как лучше всего реализовать задание таймера? Может быть, лазурные веб-задания? - person theface; 13.01.2019
comment
Слишком много для комментария, но есть много вариантов планирования обратного вызова по тайм-ауту. Зависит от того, с какими технологиями вы знакомы, сколько кода вы хотите написать. Одно из самых простых решений: запланировать сообщение в будущем в очереди хранилища Azure, настроить функцию Azure, которая будет запускаться этой очередью, а затем функция инициирует событие в боте через DirectLine. Вариант без кода: определите экземпляр приложения логики с триггером HTTP, затем действие Delay, за которым следует действие HTTP, которое снова запускает бота через DirectLine. Можно сделать и с Durable Functions. Вся поддержка отмены. - person Drew Marsh; 14.01.2019
comment
Спасибо за вашу помощь. Я изменил реализацию на ваше предложение с очередью. 1. Бот теперь отправит элемент в очередь Azure с невидимым временем сообщения X часов. 2. Функция Azure, запускаемая очередью, отправит сообщение адаптеру прямой линии бота с исходными данными беседы. 3. Бот ответит на старый разговор. Спасибо за помощь! Если я найду время, я напишу об этом в блоге... - person theface; 14.01.2019
comment
@theface, вам удалось написать какой-нибудь пост о решении или репозитории github, я был бы рад узнать, как вы реализовали решение. Я должен реализовать что-то подобное. - person Amit C; 16.12.2020