Microsoft BotFramework v4 任务计划和状态

Microsoft BotFramework v4 Task schedule and States

我正在使用 Microsoft 的 botframework v4 构建一个机器人。我已经实施了一个延迟任务,以检查用户是否在过去 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;
                        }
                    }
                }...

首先让我指出,在机器人内部启动长期存在的 Tasks 并不是一个非常可扩展的解决方案。与 Web 应用程序一样,机器人程序倾向于跨多个服务器扩展,并且还必须能够容忍进程或服务器重启。您可能希望使用某种外部分布式计时器系统,以确保无论机器人的生命周期如何,计时器都将持续存在并最终被调用。除此之外,它也没有很好地利用机器资源。如果您的机器人有 100 或希望有 1000 的用户,并且您不断地创建 TaskTask::Delay,那么您将在资源方面产生相当大的开销。通常这样的解决方案是拥有一个由单个工作人员提供服务的计时器存储。

好的,撇开警告不谈,让我们谈谈您面临的具体问题:

  1. I can not cancel this task if the user has already resetted the conversation manually over the dialogs.

嗯,你可以......你只需要创建一个同伴 CancellationTokenSource,将它的 Token 传递给 Task.DelayContinueWith,然后,如果你想取消它,调用它的 Cancel 方法,这将释放延迟计时器并确保它永远不会被调用。

我不知道你的示例代码中的 EndConversation 到底是什么,但它现在需要是一个具有 [=11= 的数据结构,而不仅仅是 Task ] 和 CancellationToken 就可以了。一个简单的元组可以在这里工作,否则你自己创建一个新的 class.

  1. The states in the Delay Task are not updated. As example, if the user adds a note to a list, the State inside the delay task is 0 at the end of the conversation.

是的,所以您看到的是陈旧状态,因为您在继续时关闭了原始 dialogContext 变量。从技术上讲,您不应该在当前回合之后使用 DialogContextITurnContext 之类的东西。

您在这里尝试做的是所谓的主动消息传递。即使您的逻辑实际上并未向用户回复消息,同样的概念也适用。所以你想要做的实际上是捕获闭包外的 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.");
    });
}