0

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 child and Dialog A child has a choice to go back to Dialog A. But it is getting a Stackoverflow Exception. When i remove one from the other: Example removing Dialog A child from Dialog A or removing Dialog A from Dialog A child, the exception error disappears. In short it throw a Stackoverflow Exception when both dialog can call each other.

I know i can just EndDialogAsync in this specific scenario to go back to Dialog A but my real dialog flow is not together like this . How to fix this?

Dialog A code:

 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();
    }

Dialog A child code:

 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();
    }

Main code that called dialog 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();
    }
user10860402
  • 754
  • 4
  • 22
  • Could you please insert the class definition too? What is the base of DialogA_child and what is the base for DialogA? – koviroli Feb 26 '19 at 12:52
  • 3
    You are recursively creating `DialogA_child` in `DialogA` constructor and `DialogA` in `DialogA_child` constructor. This will not work! – Paweł Łukasik Feb 26 '19 at 12:53
  • 1
    Possible duplicate of [How does a "stack overflow" occur and how do you prevent it?](https://stackoverflow.com/questions/26158/how-does-a-stack-overflow-occur-and-how-do-you-prevent-it) – Liam Feb 26 '19 at 12:58
  • tl;dr you code is stuck in a permanent un-exit able loop – Liam Feb 26 '19 at 13:00
  • As Paweł Łukasik noted perhaps you are willing to change your class modeling. – Ucho Feb 26 '19 at 13:04
  • @Ucho i am willing to change my modeling for better can you give example sir? I am new in bot framework and c#. – user10860402 Feb 26 '19 at 13:05
  • @user10860402 You cannot do what Paweł Łukasik told you. You have to get rid of either of the those instantiations in constructors. Start with that. – Ucho Feb 26 '19 at 13:09
  • How can i call them without creating them in the constructors? – user10860402 Feb 26 '19 at 13:12

2 Answers2

4

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. ...
koviroli
  • 1,366
  • 1
  • 11
  • 24
  • I am not yet familiar with base class sir i have updated the question with their class. – user10860402 Feb 26 '19 at 12:59
  • I kind of understand how can you prevent this sir if you want to separate your dialog? Because i want them separated to look cleaner. – user10860402 Feb 26 '19 at 13:04
  • If you do not have any reason to call `:base` (so base class' constructor), do not call it in your `DialogA_child`'s constructor. Also I just noticed, you add `DialogA_child` in your `DialogA`'s constructor and add `DialogA` in your `DialogA_child`'s constructor. This is a problem again. Have one `DialogA_child` in your `DialogA` class **XOR** one `DialogA` in your `DialogA_child`'s class because this is another reason for a recursive call. – koviroli Feb 26 '19 at 13:05
  • How to not call the base sir? i am getting an error that it is required. – user10860402 Feb 26 '19 at 13:14
  • How can i call another dialog without making an instance of them in the constructor? and how does one dialog become a base class of another dialog? in this 3 example dialog which is the base of which? – user10860402 Feb 26 '19 at 13:22
  • Why do you pasted MainDialog? MainDialog's constructor get called first, then DialogA and then call DialogA_child? You didn't write appropriate description about your problem so I only can guess what is happening. In definition `class A : B{}` , `B` is the **base class** of `A` – koviroli Feb 26 '19 at 13:23
2

@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.

mdrichardson
  • 6,656
  • 1
  • 3
  • 19
  • First thank you Mr.Richardson! I removed "AddDialog(new DialogA(DialogAId))" and I changed Dialog A`s constructor to this "public DialogA() : base(nameof(DialogA))" and added "AddDialog(new DialogA())" to DialogA_child. How can i begin DialogA now in DialogA_child? – user10860402 Feb 26 '19 at 21:42
  • See new edit, "Forcing a Dialog Loop". It's ugly and I don't really recommend it, but if you really want your dialogs to flow this way, this is how to do so. – mdrichardson Feb 26 '19 at 22:36
  • I see now. Thank you for teaching that options but i kinda see now it is bad coding and im thinking to remodel my flow so that i will just `EndDialogAsync` child A to go back to dialogA. Thank you Mr.Richardson! – user10860402 Feb 27 '19 at 00:29
  • May i ask Mr.RIchardson what happens when you don't end your dialogs? And they stacked so many times? – user10860402 Feb 27 '19 at 02:10
  • @user10860402 Dialogs are supposed to end automatically when you reach the end of the last step, but it's good practice to end them manually. If you don't and they don't close themselves, your user might not use the dialog flow you intended. – mdrichardson Feb 28 '19 at 17:35
  • @user10860402 I added an update to the end of my answer that you may find interesting. New update to the BotBuilder SDK. – mdrichardson Feb 28 '19 at 17:36
  • Sir richard i already updated my SDK to 4.3 how can i use this new addition now that can call main dialog from its child dialog? – user10860402 Apr 01 '19 at 06:11
  • this is the question. https://stackoverflow.com/questions/55449059/botframework-v4-call-parent-dialog-from-child-dialog-without-stackoverflow-exce – user10860402 Apr 01 '19 at 06:17
  • @user10860402 Please don't link me to your questions. So long as you use the `botframework` tag, somebody on my team (often me) will get to them. – mdrichardson Apr 01 '19 at 15:35