Stackoverflow Exception in a simple dialog

后端 未结 2 667
终归单人心
终归单人心 2021-01-26 12:05

Hello i am getting an Stackoverflow Exception in this two dialog. Dialog A is being called from a main dialog class. Dialog A have a choice to go to Dialog A

相关标签:
2条回答
  • 2021-01-26 12:33

    @koviroli's answer is 100% correct, so please accept his as the answer once you feel like you understand it. I'm adding this as an additional answer because it seems like you're struggling to understand things a little and comments limit me from providing good explanation.

    Quick Explanation of Constructors

    Since you're new to C#, I'll provide a quick explanation of constructors. DialogA_child's constructor is this part:

    public DialogA_child(string dialogId)
        : base(dialogId)
    {
        InitialDialogId = InitialId;
    
        WaterfallStep[] waterfallSteps = new WaterfallStep[]
         {
            FirstStepAsync,
            SecondStepAsync,
         };
        AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
        AddDialog(new DialogA(DialogAId));
    
    }
    

    Any time that you use new DialogA_child("xyz"), the constructor is called to "construct" DialogA_child. The :base(dialogId) makes it so that "xyz" gets sent to the constructor of DialogA_child's base class, which is ComponentDialog. In ComponentDialog's constructor, it sets the argument that gets passed in ("xyz", in this case) to the dialogId.

    If you click on ComponentDialog in your code and press F12, it will take you to the definition of ComponentDialog and you can see this:

    public ComponentDialog(string dialogId);.

    Here's What's Wrong

    In DialogA_child's constructor, you have: AddDialog(new DialogA(DialogAId));, which creates a new instance of DialogA. Then, in DialogA's constructor, you have AddDialog(new DialogA_child(DialogAchildId));, which create another DialogA_child, and so on and so on.

    Basically, DialogA and DialogA_child keep creating new instances of each other, causing the StackOverflow.

    The easiest fix is to just remove AddDialog(new DialogA(DialogAId));.

    Additional Notes

    Again, I know you're new to C#, so I'll help you out with a couple of other things.

    private const string ChoicePrompt = "choicePrompt";

    should probably be

    private const string ChoicePromptId = "choicePrompt";

    since ChoicePrompt is already defined as a type of prompt.

    When defining your dialog constructors, it's easiest to use something like:

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

    This will automatically set the id of DialogA to "DialogA". It will help with two things:

    1. Since dialogs have to use unique IDs, this will prevent you from accidentally calling the same dialog twice.

    2. It's easier to keep track of and you don't have to pass in a name for it. For example, to call the dialog, you could now use AddDialog(new DialogA()); instead of AddDialog(new DialogA(DialogAId));.

    Forcing a Dialog Loop

    Currently, you cannot loop dialogs the way that you want to (See update below). You cannot:

    1. Have DialogA call DialogA_child
    2. Then have DialogA_child again call DialogA.

    As you have seen, this creates a stack overflow.

    Instead, you can call it indirectly.

    Instead of having DialogA_child call DialogA, do something like:

    1. Have DialogA_child's choice prompt with an option of "Restart Dialog A" (or something unique).
    2. In OnTurnAsync (in your bot's main Class file), check to see if the user responded with "Restart Dialog A". If so, clear all dialogs (or just replace) and then begin DialogA again.

    Code:

    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 will release soon that allows any child or sibling dialog to call any dialog defined by the parent. See this pull request. I believe you can have a child dialog call a parent, as OP requested, but it's still new and I haven't tested.

    0 讨论(0)
  • 2021-01-26 12:36

    In Visual Studio you can check your call stack and you will know where is the exact problem in your StackOverflowException.

    If DialogA is the base class of DialogA_child then in your DialogA_child's constructor and your base class constructor will be calling themselfs recursively.

    So your call stack should look like this:

    1. DialogA constructor add new DialogA_child
    2. DialogA_child calls base(so DialogA constructor)
    3. DialogA constructor add new DialogA_child
    4. DialogA_child calls base(so DialogA constructor)
    5. ...
    0 讨论(0)
提交回复
热议问题