Stackoverflow Exception в простом диалоге - PullRequest
0 голосов
/ 26 февраля 2019

Здравствуйте, я получаю исключение Stackoverflow в этих двух диалогах.Dialog A вызывается из основного класса диалога.Диалоговое окно A может выбрать Dialog A child, а Dialog A child - вернуться к Dialog A.Но это становится исключением Stackoverflow.Когда я удаляю одно из другого: Пример удаления Dialog A child из Dialog A или удаления Dialog A из Dialog A child, ошибка исключения исчезает.Короче говоря, он генерирует исключение Stackoverflow, когда оба диалога могут вызывать друг друга.

Я знаю, что в этом конкретном сценарии я могу просто EndDialogAsync, чтобы вернуться к Dialog A, но мой реальный диалог не такой, как этот,Как это исправить?

Код 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();
    }

Дочерний код Dialog 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();
    }

Ответы [ 2 ]

0 голосов
/ 27 февраля 2019

@ 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 выhave: 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 на "DialogA".Это поможет с двумя вещами:

  1. Поскольку диалоги должны использовать уникальные идентификаторы, это предотвратит случайный вызов одного и того же диалога дважды.

  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, но он все еще новый, и я не проверял.

0 голосов
/ 26 февраля 2019

В Visual Studio вы можете проверить call stack, и вы узнаете, где именно проблема в вашем StackOverflowException.

Если DialogA является базовым классом DialogA_childтогда в конструкторе DialogA_child и конструкторе базового класса они будут рекурсивно вызывать себя.

Таким образом, ваш стек вызовов должен выглядеть следующим образом:

  1. DialogA конструктор add newDialogA_child
  2. DialogA_child вызывает базу (так: DialogA constructor)
  3. DialogA конструктор добавляет новый DialogA_child
  4. DialogA_child вызывает базу (так DialogA constructor)
  5. ...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...