Bot框架:循环提示

时间:2017-11-24 16:02:11

标签: c# asynchronous dialog botframework

我最近加入了微软的Bot框架,这将是我第一次接触C#中的异步编程。我正在创建一个设计为选择树的提示。使用XML文档,我设计了一个用户可以选择的主题层次结构 - 然后我使用HelpTopicSelector类抽象了XML的解析。

流程如下:

  
      
  • 用户类型“ help
  •   
  • 上传到HelpDialog的上下文
  •   
  • 帮助对话框创建提示,其中包含HelpTopicSelector提供的选项列表
  •   
  • 当用户选择提示选项时,HelpTopicSelector“选择”选择并从子树中更新新的选项列表
  •   
  • 使用更新的主题创建另一个提示
  •   
  • 重复,直到最后选择的主题是最后一个节点 - 调用Context.Done
  •   

从基本对话框中调用帮助对话框,如下所示:

    private async Task ActivityRecievedAsync(IDialogContext context, IAwaitable<object> result)
    {
        Activity activity = await result as Activity;

        if (activity.Text == "test")
        {
            await context.PostAsync("works");
        }
        else if(activity.Text == "help")
        {
            await context.Forward(new HelpDialog(), this.ResumeAfterHelp, activity.AsMessageActivity(), System.Threading.CancellationToken.None);
            await context.PostAsync("Done Selection!");
        }

        context.Wait(ActivityRecievedAsync);
    }

我几乎可以肯定我的代码中的问题在于我的HelpDialog的“循环”性质,但我真的不知道为什么它会失败。

class HelpDialog : IDialog
{
    public async Task StartAsync(IDialogContext context)
    {
        await context.PostAsync("Reached Help Dialog!");
        context.Wait(ActivityRecievedAsync);
    }

    private async Task ActivityRecievedAsync(IDialogContext context, IAwaitable<object> result)
    {
        var message = await result;
        await context.PostAsync("HelpDialog: Activity Received");
        await HandleTopicSelection(context);

        context.Wait(ActivityRecievedAsync);
    }

    private async Task HandleTopicSelection(IDialogContext context)
    {
        List<string> topics = HelpTopicSelector.Instance.Topics;
        PromptDialog.Choice<string>(context, TopicSelectedAsync, topics, "Select A Topic:");

        // Unecessary?
        context.Wait(ActivityRecievedAsync);
    }

    private async Task TopicSelectedAsync(IDialogContext context, IAwaitable<string> result)
    {
        string selection = await result;

        if (HelpTopicSelector.Instance.IsQuestionNode(selection))
        {
            await context.PostAsync($"You asked: {selection}");
            HelpTopicSelector.Instance.Reset();
            context.Done<string>(selection);
        }
        else
        {
            HelpTopicSelector.Instance.SelectElement(selection);
            await HandleTopicSelection(context);
        }

        // Unecessary?
        context.Wait(ActivityRecievedAsync);
    }
}

我的期望:

  

  • 我相信await关键字应该执行Task的执行,直到等待的Task完成。
  • 类似地,我相信在任务结束时调用Context.Wait以循环回AcitivtyReceived方法,这有效地使机器人等待用户输入。
  • 假设逻辑为真,帮助对话框输入 StartAsync 方法,并将控制权交给 ActivityReceivedAsync ,后者响应上下文传递的“消息”父对话框的.Forward 。然后,它等待负责提示的 HandleTopic 方法。提示继续在 TopicSelectedAsync 中执行,如ResumeAfter参数所示。
  • TopicSelectedAsync 方法检查所选主题是否位于XML树的末尾,如果是,则通过调用Context.Done结束Dialog。否则,它等待另一个HandleTopic方法,该方法以递归方式创建另一个提示 - 在对话结束之前有效地创建一个循环。

鉴于这看起来有多酷,我对面对错误并不感到惊讶。机器人模拟器抛出“ Stack is Empty ”异常

。 尝试使用断点进行调试后,我注意到HelpDialog突然结束并在进入 TopicSelectedAsync 方法时退出(特别是在等待结果时)。 Visual Studio抛出以下异常:

  

无效需求:预期通话,有民意调查。

EXTRA注意: 我最初尝试在BasicDialog类中编写这个逻辑而不转发到任何其他对话框。令我惊讶的是,它几乎完美无缺。

1 个答案:

答案 0 :(得分:2)

此调查对话框示例与您的场景类似:https://github.com/Microsoft/BotBuilder-Samples/blob/45d0f8767d6b71b3a11b060c893521d5150ede7f/CSharp/core-proactiveMessages/startNewDialogWithPrompt/SurveyDialog.cs

将其修改为帮助对话框:

Name

从像这样的父对话框调用它(使用context.Call()而不是.Forward()):

[Serializable]
public class HelpDialog : IDialog
{
     public async Task StartAsync(IDialogContext context)
    {
        PromptDialog.Choice<string>(context, TopicSelectedAsync, HelpTopicSelector.Instance.Topics, "Select A Topic:", attempts: 3, retry: "Please select a Topic");
    }

    private async Task TopicSelectedAsync(IDialogContext context, IAwaitable<object> result)
    {
        try
        {
            string selection = await result as string;

            if (HelpTopicSelector.Instance.IsQuestionNode(selection))
            {
                await context.PostAsync($"You asked: {selection}");
                HelpTopicSelector.Instance.Reset();
                context.Done<string>(selection);
            }
            else
            {
                await this.StartAsync(context);
            }
        }
        catch (TooManyAttemptsException)
        {
            await this.StartAsync(context);
        }                
    }
}

为Context.Wait()提供方法时,实际上是在提供一个continuation委托。从用户收到的下一条消息将最后发送到该方法.Wait()&#39; ed。如果您要转发或调用单独的对话框,则父级也不应该调用.Wait()。此外,在调用context.Done()时,之后在同一对话框中也不应该有.Wait()。