Stackoverflow在一个简单的对话框中的异常

问题描述 投票:0回答:2

您好,我在这两个对话框中收到了Stackoverflow异常。从主对话类调用Dialog A。 Dialog A可以选择去Dialog A childDialog A child可以选择回到Dialog A。但它正在获得Stackoverflow异常。当我从另一个中删除一个:从Dialog A child删除Dialog A或从Dialog A删除Dialog A child的示例,异常错误消失。简而言之,当两个对话框都可以相互调用时,它会抛出Stackoverflow异常。

我知道我可以在这个特定情况下EndDialogAsync回到Dialog A,但我真正的对话流不是这样的。如何解决这个问题?

对话A代码:

 public class DialogA : ComponentDialog
{
    private const string InitialId = "dialogA";
    private const string ChoicePrompt = "choicePrompt";
    private const string DialogAchildId = "dialogA_childId";

    public DialogA(string dialogId)
        : base(dialogId)
    {
        InitialDialogId = InitialId;

        WaterfallStep[] waterfallSteps = new WaterfallStep[]
         {
            FirstStepAsync,
            SecondStepAsync,
            ThirdStepAsync,
         };
        AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
        AddDialog(new ChoicePrompt(ChoicePrompt));
        AddDialog(new DialogA_child(DialogAchildId));

    }

    private static async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        return await stepContext.PromptAsync(
            ChoicePrompt,
            new PromptOptions
            {
                Prompt = MessageFactory.Text($"Here are your choices:"),
                Choices = new List<Choice>{new Choice { Value = "Open Dialog A_Child", }, new Choice { Value = "Open Dialog B_Child" }, },
                RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
            });
    }

    private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        var response = (stepContext.Result as FoundChoice)?.Value.ToLower();

        if (response == "open dialog a_child")
        {
            return await stepContext.BeginDialogAsync(DialogAchildId, cancellationToken: cancellationToken);
        }

        return await stepContext.NextAsync();
    }

    private static async Task<DialogTurnResult> ThirdStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        return await stepContext.EndDialogAsync();
    }

对话框子代码:

 public class DialogA_child : ComponentDialog
{
  private const string InitialId = "dialogAchild";
    private const string ChoicePrompt = "choicePrompt";
    private const string DialogAId = "dialogAId";

    public DialogA_child(string dialogId)
        : base(dialogId)
    {
        InitialDialogId = InitialId;

        WaterfallStep[] waterfallSteps = new WaterfallStep[]
         {
            FirstStepAsync,
            SecondStepAsync,
         };
        AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
        AddDialog(new DialogA(DialogAId));

    }

    private static async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        return await stepContext.PromptAsync(
       ChoicePrompt,
       new PromptOptions
       {
           Prompt = MessageFactory.Text($"Here are your choices:"),
           Choices = new List<Choice> {new Choice { Value = "Open Dialog A" }, new Choice { Value = "Open Dialog B" }, },
           RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
       });
    }

    private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        var response = (stepContext.Result as FoundChoice)?.Value.ToLower();

        if (response == "open dialog a")
        {
            return await stepContext.BeginDialogAsync(DialogAId, cancellationToken: cancellationToken);
        }

        return await stepContext.NextAsync();
    }

调用对话框A的主代码:

   public class MainDialog : ComponentDialog
{
    private const string InitialId = "mainDialog";
    private const string ChoicePrompt = "choicePrompt";
    private const string DialogAId = "dialogAId";

    public MainDialog(string dialogId)
        : base(dialogId)
    {
        InitialDialogId = InitialId;

        WaterfallStep[] waterfallSteps = new WaterfallStep[]
         {
            FirstStepAsync,
            SecondStepAsync,
            ThirdStepAsync,
         };
        AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
        AddDialog(new ChoicePrompt(ChoicePrompt));
        AddDialog(new DialogA(DialogAId));
    }

    private static async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        return await stepContext.PromptAsync(
            ChoicePrompt,
            new PromptOptions
            {
                Prompt = MessageFactory.Text($"Here are your choices:"),
                Choices = new List<Choice>{ new Choice { Value = "Open Dialog A" }, new Choice { Value = "Open Dialog B" }, },
                RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
            });
    }

    private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        var response = (stepContext.Result as FoundChoice)?.Value.ToLower();

        if (response == "open dialog a")
        {
            return await stepContext.BeginDialogAsync(DialogAId, cancellationToken: cancellationToken);
        }

        if (response == "open dialog b")
        {
        }

        return await stepContext.NextAsync();
    }

    private static async Task<DialogTurnResult> ThirdStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {

        return await stepContext.EndDialogAsync();
    }
c# botframework stack-overflow
2个回答
4
投票

在Visual Studio中,您可以检查您的call stack,您将知道StackOverflowException中的确切问题。

如果DialogA是DialogA_child的基类,那么在你的DialogA_child的构造函数中,你的基类构造函数将递归地调用它们自己。

所以你的调用堆栈应如下所示:

  1. DialogA构造函数添加新的DialogA_child
  2. DialogA_child称基地(所以DialogA constructor
  3. DialogA构造函数添加新的DialogA_child
  4. DialogA_child称基地(所以DialogA constructor
  5. ...

1
投票

@ koviroli的回答是100%正确的,所以一旦你理解了它,请接受他作为答案。我将此作为一个额外的答案添加,因为看起来你似乎很难理解一些事情并且评论限制我提供好的解释。

Quick Explanation of Constructors

由于您是C#的新手,我将提供构造函数的快速解释。 DialogA_child的构造函数是这一部分:

public DialogA_child(string dialogId)
    : base(dialogId)
{
    InitialDialogId = InitialId;

    WaterfallStep[] waterfallSteps = new WaterfallStep[]
     {
        FirstStepAsync,
        SecondStepAsync,
     };
    AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
    AddDialog(new DialogA(DialogAId));

}

任何时候你使用new DialogA_child("xyz"),构造函数被称为“构造”DialogA_child:base(dialogId)使得“xyz”被发送到DialogA_child基类的构造函数,即ComponentDialog。在ComponentDialog的构造函数中,它设置了传入的参数(在本例中为“xyz”)到dialogId。

如果您在代码中单击ComponentDialog并按F12,它将带您进入ComponentDialog的定义,您可以看到:

public ComponentDialog(string dialogId);

Here's What's Wrong

DialogA_child的构造函数中,你有:AddDialog(new DialogA(DialogAId));,它创建了DialogA的新实例。然后,在DialogA的构造函数中,你有AddDialog(new DialogA_child(DialogAchildId));,它创建另一个DialogA_child,依此类推。

基本上,DialogADialogA_child不断创建彼此的新实例,导致StackOverflow。

最简单的解决方法是删除AddDialog(new DialogA(DialogAId));

Additional Notes

再一次,我知道你是C#的新手,所以我会帮助你解决其他一些问题。

private const string ChoicePrompt = "choicePrompt";

应该是

private const string ChoicePromptId = "choicePrompt";

因为ChoicePrompt已被定义为一种提示。

在定义对话框构造函数时,最简单的方法是使用以下内容:

public DialogA() : base(nameof(DialogA))

这将自动将DialogA的id设置为“DialogA”。它有两个方面:

  1. 由于对话框必须使用唯一ID,这将防止您意外地两次调用同一对话框。
  2. 跟踪更容易,您不必为其传递名称。例如,要调用对话框,您现在可以使用AddDialog(new DialogA());而不是AddDialog(new DialogA(DialogAId));

Forcing a Dialog Loop

目前,您无法以您希望的方式循环对话框 (见下面的更新)。你不能:

  1. DialogA打电话给DialogA_child
  2. 然后让DialogA_child再次打电话给DialogA

如您所见,这会产生堆栈溢出。

相反,你可以间接称呼它。

而不是让DialogA_child调用DialogA,做类似的事情:

  1. DialogA_child的选择提示,选择“重新启动对话框A”(或独特的东西)。
  2. OnTurnAsync中(在机器人的主类文件中),检查用户是否以“Restart Dialog A”响应。如果是这样,清除所有对话框(或只是替换),然后再次开始DialogA

码:

DialogA_child

private static async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
    return await stepContext.PromptAsync(
    choicePrompt,
    new PromptOptions
    {
        Prompt = MessageFactory.Text($"Here are your choices:"),
        Choices = new List<Choice> { new Choice { Value = "Restart Dialog A" }, new Choice { Value = "Open Dialog B" }, },
        RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
    });
}

<myBot>.cs

public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
    var activity = turnContext.Activity;

    var dc = await Dialogs.CreateContextAsync(turnContext);

    if (activity.Text == "Restart Dialog A")
    {
        await dc.CancelAllDialogsAsync();
        await dc.BeginDialogAsync(nameof(DialogA));
    }

Update

BotBuilder SDK V4.3即将发布,允许任何子对话或兄弟对话框调用父对象定义的任何对话框。见this pull request。我相信你可以像OP要求的那样召唤一个子对话呼叫父母,但它仍然是新的,我还没有测试过。

© www.soinside.com 2019 - 2024. All rights reserved.