简单对话框中的stackoverflow异常



你好,我在这两个对话框中得到了一个stackoverflow异常。Dialog A是从主对话类别的。对话框A可以选择转到Dialog A child,而Dialog A child可以选择返回Dialog A。但是它得到了stackoverflow异常。当我从另一个中删除一个:示例从Dialog A删除Dialog A child或从Dialog A child删除Dialog A时,异常错误消失了。简而言之

我知道我只能在这种特定情况下EndDialogAsync返回Dialog 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();
    }

称为对话框的主代码:

   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的构造函数中,您的基类构造函数将递归地称其为自己。

所以您的呼叫堆栈应该看起来像这样:

  1. DialogA构造函数添加新的DialogA_child
  2. DialogA_child调用基础(SO DialogA constructor
  3. DialogA构造函数添加新的DialogA_child
  4. DialogA_child调用基础(SO 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"),构造函数都会称为"构造" DialogA_child:base(dialogId)使得" XYZ"被发送到DialogA_child的基类的构造函数,即ComponentDialog。在ComponentDialog的构造函数中,它设置了传递(在这种情况下为" xyz")的参数到对话。

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

public ComponentDialog(string dialogId);

这是什么问题

DialogA_child的构造函数中,您有:AddDialog(new DialogA(DialogAId));,它创建了DialogA的新实例。然后,在DialogA的构造函数中,您有AddDialog(new DialogA_child(DialogAchildId));,它可以创建 eather 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的选择提示
  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将很快发布,该v4.3允许任何儿童或兄弟姐妹对话框拨打父母定义的任何对话框。请参阅此拉的请求。我相信您可以按照OP的要求打电话给父母,但它仍然是新事物,但我尚未进行测试。

最新更新