繁体   English   中英

简单对话框中的Stackoverflow异常

[英]Stackoverflow Exception in a simple dialog

您好,我在这两个对话框中收到Stackoverflow异常。 从主对话框类中调用Dialog A 对话框A可以选择转到“ Dialog A child而“ Dialog A child可以选择返回到“ Dialog A 但是它正在收到一个Stackoverflow异常。 当我从另一个中删除一个时:从Dialog A删除Dialog A child或从Dialog A child删除Dialog A示例,异常错误消失。 简而言之,当两个对话框可以相互调用时,它将引发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();
    }

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

如果DialogA是基类的DialogA_child然后在您的DialogA_child的构造和基类的构造函数将递归调用themselfs。

因此,您的调用堆栈应如下所示:

  1. DialogA构造函数添加新的DialogA_child
  2. DialogA_child调用base(因此DialogA constructor
  3. DialogA构造函数添加新的DialogA_child
  4. DialogA_child调用base(因此DialogA constructor
  5. ...

@koviroli的答案是100%正确的,因此,一旦您感到理解,请接受他的答案。 我将其添加为其他答案,因为您似乎在努力理解一些东西,而评论限制了我提供良好的解释。

构造函数快速说明

由于您是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") ,都会将构造函数调用为“ construct” DialogA_child :base(dialogId)使之能够将“ xyz”发送到DialogA_child的基类的构造函数ComponentDialog ComponentDialog的构造函数中,它将传入的参数(在本例中为“ xyz”)设置为dialogId。

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

public ComponentDialog(string dialogId);

这是错的地方

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

基本上, DialogADialogA_child继续创建彼此的新实例,从而导致StackOverflow。

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

补充笔记

同样,我知道您是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));

强制对话框循环

当前,您无法以所需的方式循环对话框 (请参阅下面的更新)。 你不能:

  1. DialogA调用DialogA_child
  2. 然后让DialogA_child再次调用DialogA

如您所见,这会导致堆栈溢出。

相反,您可以间接调用它。

不用让DialogA_child调用DialogA ,而是执行以下操作:

  1. 具有DialogA_child的选择提示,并带有“重新启动对话框A”(或唯一的选项)选项。
  2. OnTurnAsync (您的机器人主类文件中)中,检查用户是否通过“重新启动对话框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));
    }

更新资料

BotBuilder SDK V4.3即将发布,它允许任何子对话框或同级对话框调用父级定义的任何对话框。 请参阅此拉取请求 相信您可以按照OP的要求将子对话框称为父对话框,但是它仍然是新的,并且我还没有测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM