Несколько сообщений боту в быстрой последовательности приводят к его краху

Настройка

У меня есть бот, который работает на .NET + Bot Framework + Azure + Facebook Messenger.

Исходная проблема

Я пытался решить проблему, когда отправка нескольких сообщений боту вызывает исключение и ошибку HTTP 412. Microsoft описывает эту проблему здесь: https://docs.microsoft.com/en-us/bot-framework/troubleshoot-general-problems#what-causes-an-error-with-http-status-code-412-precondition-failed-or-http-status-code-409-conflict

Первое решение

На странице выше Microsoft предоставляет устаревший пример кода для решения этой проблемы. В этой проблеме github есть исправленная версия этого кода, которая должна работать. . Я поместил его в конструктор моего MessageController:

    static MessagesController()
    {
        // Prevent exception in the bot and HTTP error 412 when the user
        // sends multiple messages in quick succession. This may cause 
        // potential problems with consistency of getting/setting user
        // properties. 
        // See https://docs.microsoft.com/en-us/bot-framework/troubleshoot-general-problems#what-causes-an-error-with-http-status-code-412-precondition-failed-or-http-status-code-409-conflict
        // for details. The above link contains wrong code sample, revised
        // code is from here: https://github.com/Microsoft/BotBuilder/issues/2345
        var builder = new ContainerBuilder();
        builder
            .Register(c => new CachingBotDataStore(c.ResolveKeyed<IBotDataStore<BotData>>(typeof(ConnectorStore)), CachingBotDataStoreConsistencyPolicy.LastWriteWins))
            .As<IBotDataStore<BotData>>()
            .AsSelf()
            .InstancePerLifetimeScope();
        builder.Update(Conversation.Container);
    } 

Вторая проблема

Теперь исключение по-прежнему возникает, когда я быстро отправляю несколько сообщений боту. Однако он изменился с ошибки HTTP 412 на что-то другое:

Произошла одна или несколько ошибок. в System.Threading.Tasks.Task.ThrowIfExceptional (логическое значение includeTaskCanceledExceptions) в System.Threading.Tasks.Task1.GetResultCore (логическое значение waitCompletionNotification) в System.Threading.Tasks.Task1.get_Result() в MyBot.SetUserDataProperty (активность действия, String PropertyName) , строка ValueToSet) в C:\Users\xxx.cs:строка 230

Обновление: я проверил InnerException вышеизложенного, и оказалось, что это та же самая старая ошибка HTTP 412:

Удаленный сервер возвратил ошибку: (412) Precondition Failed.

Нарушающий код — это функция, которая записывает данные в хранилище бота. Строка 230, упомянутая выше, является последней строкой этой функции:

    public static void SetUserDataProperty(Activity activity, string PropertyName, string ValueToSet)
    {
        StateClient client = activity.GetStateClient();
        BotData userData = client.BotState.GetUserData(activity.ChannelId, activity.From.Id);
        userData.SetProperty<string>(PropertyName, ValueToSet);

        //client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
        // Await async call without making the function asynchronous:
        var temp = Task.Run(() => client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData)).Result;
    }

Вопрос

Что еще я могу сделать, чтобы убедиться, что пользователь может отправлять несколько сообщений в быстрой последовательности, не вызывая исключения при записи в хранилище BotState?


person Community    schedule 05.09.2017    source источник
comment
пожалуйста, опубликуйте код, чтобы воспроизвести проблему   -  person Ezequiel Jadib    schedule 05.09.2017
comment
вы используете клиент состояния по умолчанию?   -  person D4RKCIDE    schedule 05.09.2017
comment
@JasonSowers Не уверен. Я использую это, чтобы сохранить пары ключ-значение в состояние бота: context.UserData.SetValue<string>(PropertyName, ValueToSet);   -  person    schedule 05.09.2017
comment
Еще один вопрос, вы вызываете этот метод SetUserDataProperty в диалоге?   -  person D4RKCIDE    schedule 05.09.2017


Ответы (1)


Я думаю, что здесь есть несколько проблем

То, как вы пытаетесь это сделать activity.GetStateClient();, предназначено только для прототипирования. Мы не рекомендуем этот метод для кода производственного уровня. Вы можете установить пользовательские данные, такие как context.UserData.SetValue("food", "Nachos" );, в диалоговом окне, и значения будут автоматически сохранены при сериализации диалогового окна.

Скорее всего, вы вызываете этот метод SetUserDataProperty из диалогового окна, поэтому, когда вы делаете это var temp = Task.Run(() => client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData)).Result;, он конфликтует и вызывает ошибку.

ознакомьтесь с этой публикацией в блоге, чтобы узнать больше.

Вот как реализовать ваш дополнительный вопрос:

        if (activity.Type == ActivityTypes.Message)
        {

            var message = activity as IMessageActivity;
            using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
            {
                var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
                var key = new AddressKey()
                {
                    BotId = message.Recipient.Id,
                    ChannelId = message.ChannelId,
                    UserId = message.From.Id,
                    ConversationId = message.Conversation.Id,
                    ServiceUrl = message.ServiceUrl
                };
                ConversationReference r = new ConversationReference();
                var userData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None);

                userData.SetProperty("key 1", "value1");
                userData.SetProperty("key 2", "value2");

                await botDataStore.SaveAsync(key, BotStoreType.BotUserData, userData, CancellationToken.None);
                await botDataStore.FlushAsync(key, CancellationToken.None);
            }
            await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
        }

вам нужно будет реализовать этот класс или что-то подобное:

public class AddressKey : IAddress
{
    public string BotId { get; set; }
    public string ChannelId { get; set; }
    public string ConversationId { get; set; }
    public string ServiceUrl { get; set; }
    public string UserId { get; set; }
}
person D4RKCIDE    schedule 05.09.2017
comment
Спасибо! Но тогда есть ли рекомендуемый способ установить пользовательские данные из MessageController, когда IDialogContext еще не доступен? - person ; 06.09.2017
comment
Да, я отредактирую свой ответ, чтобы показать вам. Надеюсь это поможет. Удачи! - person D4RKCIDE; 06.09.2017