您好,我在这两个对话框中收到了Stackoverflow异常。从主对话类调用Dialog A
。 Dialog A可以选择去Dialog A child
,Dialog 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();
}
在Visual Studio中,您可以检查您的call stack
,您将知道StackOverflowException
中的确切问题。
如果DialogA是DialogA_child
的基类,那么在你的DialogA_child
的构造函数中,你的基类构造函数将递归地调用它们自己。
所以你的调用堆栈应如下所示:
DialogA
构造函数添加新的DialogA_child
DialogA_child
称基地(所以DialogA constructor
)DialogA
构造函数添加新的DialogA_child
DialogA_child
称基地(所以DialogA constructor
)@ 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”)到dialogId。
如果您在代码中单击ComponentDialog
并按F12,它将带您进入ComponentDialog
的定义,您可以看到:
public ComponentDialog(string dialogId);
。
在DialogA_child
的构造函数中,你有:AddDialog(new DialogA(DialogAId));
,它创建了DialogA
的新实例。然后,在DialogA
的构造函数中,你有AddDialog(new DialogA_child(DialogAchildId));
,它创建另一个DialogA_child
,依此类推。
基本上,DialogA
和DialogA_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”。它有两个方面:
AddDialog(new DialogA());
而不是AddDialog(new DialogA(DialogAId));
。目前,您无法以您希望的方式循环对话框 (见下面的更新)。你不能:
DialogA
打电话给DialogA_child
DialogA_child
再次打电话给DialogA
。如您所见,这会产生堆栈溢出。
相反,你可以间接称呼它。
而不是让DialogA_child
调用DialogA
,做类似的事情:
DialogA_child
的选择提示,选择“重新启动对话框A”(或独特的东西)。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));
}
BotBuilder SDK V4.3即将发布,允许任何子对话或兄弟对话框调用父对象定义的任何对话框。见this pull request。我相信你可以像OP要求的那样召唤一个子对话呼叫父母,但它仍然是新的,我还没有测试过。