简单对话框中的 Stackoverflow 异常
Stackoverflow Exception in a simple dialog
你好,我在这两个对话框中遇到了 Whosebug 异常。 Dialog A
正在从主对话框 class 调用。对话框 A 可以选择转到 Dialog A child
,而 Dialog A child
可以选择返回 Dialog A
。但它正在获得 Whosebug 异常。当我从另一个中删除一个时:示例从 Dialog A
中删除 Dialog A child
或从 Dialog A child
中删除 Dialog A
,异常错误消失。简而言之,当两个对话框可以相互调用时,它会抛出 Whosebug 异常。
我知道我可以 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();
}
对话框A子代码:
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
你会知道你的WhosebugException
.[=22中的确切问题在哪里=]
如果 DialogA 是 DialogA_child
的基础 class,那么在您的 DialogA_child
构造函数中,您的基础 class 构造函数将递归调用它们自己。
因此您的调用堆栈应如下所示:
DialogA
构造函数添加新的DialogA_child
DialogA_child
调用 base(所以 DialogA constructor
)
DialogA
构造函数添加新的DialogA_child
DialogA_child
调用 base(所以 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")
,都会调用构造函数 "construct" DialogA_child
。 :base(dialogId)
使得 "xyz" 被发送到 DialogA_child
的基础 class 的构造函数,即 ComponentDialog
。在 ComponentDialog
的构造函数中,它将传入的参数(在本例中为 "xyz")设置为 dialogId。
如果您在代码中单击 ComponentDialog
并按 F12,它将带您进入 ComponentDialog
的定义,您可以看到:
public ComponentDialog(string dialogId);
.
这里有什么问题
在 DialogA_child
的构造函数中,您有:AddDialog(new DialogA(DialogAId));
,它创建了 DialogA
的 new 实例。然后,在 DialogA
的构造函数中,您有 AddDialog(new DialogA_child(DialogAchildId));
,它创建 另一个 DialogA_child
,依此类推。
基本上,DialogA
和 DialogA_child
不断创建彼此的新实例,导致 Whosebug。
最简单的解决方法是只删除 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
的选择提示和 "Restart Dialog A" 选项(或独特的东西)。
- 在
OnTurnAsync
中(在您的机器人的主 Class 文件中),检查用户是否使用 "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 的要求让子对话调用父对话,但它仍然是新的,我还没有测试过。
你好,我在这两个对话框中遇到了 Whosebug 异常。 Dialog A
正在从主对话框 class 调用。对话框 A 可以选择转到 Dialog A child
,而 Dialog A child
可以选择返回 Dialog A
。但它正在获得 Whosebug 异常。当我从另一个中删除一个时:示例从 Dialog A
中删除 Dialog A child
或从 Dialog A child
中删除 Dialog A
,异常错误消失。简而言之,当两个对话框可以相互调用时,它会抛出 Whosebug 异常。
我知道我可以 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();
}
对话框A子代码:
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
你会知道你的WhosebugException
.[=22中的确切问题在哪里=]
如果 DialogA 是 DialogA_child
的基础 class,那么在您的 DialogA_child
构造函数中,您的基础 class 构造函数将递归调用它们自己。
因此您的调用堆栈应如下所示:
DialogA
构造函数添加新的DialogA_child
DialogA_child
调用 base(所以DialogA constructor
)DialogA
构造函数添加新的DialogA_child
DialogA_child
调用 base(所以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")
,都会调用构造函数 "construct" DialogA_child
。 :base(dialogId)
使得 "xyz" 被发送到 DialogA_child
的基础 class 的构造函数,即 ComponentDialog
。在 ComponentDialog
的构造函数中,它将传入的参数(在本例中为 "xyz")设置为 dialogId。
如果您在代码中单击 ComponentDialog
并按 F12,它将带您进入 ComponentDialog
的定义,您可以看到:
public ComponentDialog(string dialogId);
.
这里有什么问题
在 DialogA_child
的构造函数中,您有:AddDialog(new DialogA(DialogAId));
,它创建了 DialogA
的 new 实例。然后,在 DialogA
的构造函数中,您有 AddDialog(new DialogA_child(DialogAchildId));
,它创建 另一个 DialogA_child
,依此类推。
基本上,DialogA
和 DialogA_child
不断创建彼此的新实例,导致 Whosebug。
最简单的解决方法是只删除 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
的选择提示和 "Restart Dialog A" 选项(或独特的东西)。 - 在
OnTurnAsync
中(在您的机器人的主 Class 文件中),检查用户是否使用 "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 的要求让子对话调用父对话,但它仍然是新的,我还没有测试过。