簡體   English   中英

簡單對話框中的Stackoverflow異常

[英]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。

因此,您的調用堆棧應如下所示:

  1. DialogA構造函數添加新的DialogA_child
  2. DialogA_child調用base(因此DialogA constructor
  3. DialogA構造函數添加新的DialogA_child
  4. DialogA_child調用base(因此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") ,都會將構造函數調用為“ 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 ,依此類推。

基本上, 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的選擇提示,並帶有“重新啟動對話框A”(或唯一的選項)選項。
  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即將發布,它允許任何子對話框或同級對話框調用父級定義的任何對話框。 請參閱此拉取請求 相信您可以按照OP的要求將子對話框稱為父對話框,但是它仍然是新的,並且我還沒有測試。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM