[英]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。
因此,您的調用堆棧應如下所示:
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
的基類的構造函數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”。 這將有助於兩件事:
由於對話框必須使用唯一的ID,因此可以防止您意外地兩次調用同一對話框。
跟蹤更容易,而且您不必為此輸入名稱。 例如,要調用對話框,您現在可以使用AddDialog(new DialogA());
而不是AddDialog(new DialogA(DialogAId));
。
當前,您無法以所需的方式循環對話框
(請參閱下面的更新)。 你不能:
DialogA
調用DialogA_child
DialogA_child
再次調用DialogA
。 如您所見,這會導致堆棧溢出。
相反,您可以間接調用它。
不用讓DialogA_child
調用DialogA
,而是執行以下操作:
DialogA_child
的選擇提示,並帶有“重新啟動對話框A”(或唯一的選項)選項。 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.