你好,我在这两个对话框中得到了一个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
的构造函数中,您的基类构造函数将递归地称其为自己。
所以您的呼叫堆栈应该看起来像这样:
-
DialogA
构造函数添加新的DialogA_child
-
DialogA_child
调用基础(SODialogA constructor
) -
DialogA
构造函数添加新的DialogA_child
-
DialogA_child
调用基础(SODialogA 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")的参数到对话。
如果您单击代码中的ComponentDialog
并按F12,它将带您进入ComponentDialog
的定义,您可以看到:
public ComponentDialog(string dialogId);
。
这是什么问题
在DialogA_child
的构造函数中,您有:AddDialog(new DialogA(DialogAId));
,它创建了DialogA
的新实例。然后,在DialogA
的构造函数中,您有AddDialog(new DialogA_child(DialogAchildId));
,它可以创建 eather 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"。它将有助于两件事:
由于对话框必须使用唯一的ID,这将阻止您意外调用相同的对话框。
跟踪更容易,您不必为此而来。例如,要调用对话框,您现在可以使用
AddDialog(new DialogA());
而不是AddDialog(new DialogA(DialogAId));
。
强制对话框循环
当前,您无法以要的方式循环对话(请参见下面的更新)。你不能:
- 有
DialogA
致电DialogA_child
- 然后再次让
DialogA_child
致电DialogA
。
如您所见,这会产生堆栈溢出。
,您可以间接地称其为。
而不是让DialogA_child
调用DialogA
,而是做类似的事情:
- 具有
DialogA_child
的选择提示 - 在
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的要求打电话给父母,但它仍然是新事物,但我尚未进行测试。