TimerCallback 函数在部署的环境 C 中未命中#

TimerCallback function not getting hit in deployed environment C#

提问人:Malik Tanzeel 提问时间:9/26/2022 最后编辑:Malik Tanzeel 更新时间:9/27/2022 访问量:26

问:

我有一个Microsoft聊天机器人C#代码,它有一个TimerCallback函数,该函数在用户一段时间后运行。在本地运行代码时,此函数会命中,但在 Azure 环境中部署时,同一函数不会命中。

对于代码中过多的日志记录行,这只是为了验证函数是否在部署的环境中被击中。

以下是完整的代码:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Lebara.Crm.Bot.Core.Data;
using Lebara.Crm.Bot.Core.ServiceContracts;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.Extensions.Logging;

namespace Lebara.Crm.Bot.Services
{
    public class ChatSessionTimeoutMiddleware : ActivityHandler, IMiddleware
    {
        private readonly IDistributedCache _distributedCache;
        private readonly string _cachekey;
        private readonly BotOptions _botOptions;
        private readonly ISystemMessageSender _systemMessageSender;
        private readonly CustomConversationStateAccessors _customConversationStateAccessors;
        private readonly ITelemetryManager _telemetryManager;
        private readonly ISessionHandler _sessionHandler;
        private readonly IServiceProvider _serviceProvider;
        private static ConcurrentDictionary<string, Timer> _sessionTimers = new ConcurrentDictionary<string, Timer>();
        private readonly IBotFrameworkHttpAdapter _adapter;
        private readonly string _appId;
        private readonly ConcurrentDictionary<string, ConversationReference> _conversationReferences;
        private string ObjectKey;
        private readonly TelemetryClient telemetry = new TelemetryClient();
        private readonly ILogger<ChatSessionTimeoutMiddleware> _logger;

        //  Timer related variables, made global, to avoid collected by Garbage Collector as they were created in memory previously.
        private Timer _warningTimer = null;
        private Timer _warningTimerGCIssue = null;
        private Timer _timer = null;
        private Timer _timerGCIssue = null;

        public ChatSessionTimeoutMiddleware(
            IDistributedCache distributedCache,
            IConfiguration configuration,
            IOptions<BotOptions> options,
            ISystemMessageSender systemMessageSender,
            CustomConversationStateAccessors customConversationStateAccessors,
            ITelemetryManager telemetryManager,
            ISessionHandler sessionHandler,
            IServiceProvider serviceProvider,
            IBotFrameworkHttpAdapter adapter,
            ConcurrentDictionary<string, ConversationReference> conversationReferences,
            ILogger<ChatSessionTimeoutMiddleware> logger)
        {
            _cachekey = $"{configuration["RedisCachingRoot"]}session-timeout:";
            _distributedCache = distributedCache;
            _botOptions = options.Value;
            _systemMessageSender = systemMessageSender;
            _customConversationStateAccessors = customConversationStateAccessors;
            _telemetryManager = telemetryManager;
            _sessionHandler = sessionHandler;
            _serviceProvider = serviceProvider;
            _adapter = adapter;
            _conversationReferences = conversationReferences;
            _appId = configuration["MicrosoftAppId"] ?? string.Empty;
            _logger = logger;

        }

        private void AddConversationReference(Activity activity)
        {
            var conversationReference = activity.GetConversationReference();
            _conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
        }

        public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate nextTurn, CancellationToken cancellationToken)
        {
            _logger.LogDebug("chat session timeout middleware");
            //telemetry.TrackEvent("ChatSessionMiddleware - OnTurnAsync");
            //telemetry.TrackTrace("ChatSessionMiddleware - OnTurnAsync", SeverityLevel.Warning, null);
            var customConversationState = await _customConversationStateAccessors.CustomConversationState.GetAsync(turnContext, () => new CustomConversationState());
            var hasChatSessionRunning = await _sessionHandler.HasRunningSessionAsync(turnContext.Activity.Conversation.Id);

            if (turnContext.Activity.Type == ActivityTypes.Message
                && !string.IsNullOrEmpty(turnContext.Activity.Text)
                && !hasChatSessionRunning)
            {
                _logger.LogDebug("chatsessiontimeout OnTurnAsync if statement");
                var key = _cachekey + turnContext.Activity.Conversation.Id;
                _logger.LogDebug($"Key {key}");
                var warningKey = "warning_" + key;
                _logger.LogDebug($"WarningKey {warningKey}");
                var period = _botOptions.InactivityPeriod;
                _logger.LogDebug($"chatsessiontimeout period {period}");
                var warningPeriod = _botOptions.WarningInactivityPeriod;
                _logger.LogDebug($"chatsessiontimeout warningPeriod {warningPeriod}");
                var cacheOptions = new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = period.Add(period)
                };
                _logger.LogDebug($"cacheOptions {cacheOptions}");
                AddConversationReference(turnContext.Activity as Activity);

                await _distributedCache.SetStringAsync(key, JsonConvert.SerializeObject(DateTime.Now.Add(period)), cacheOptions);
                await _distributedCache.SetStringAsync(warningKey, JsonConvert.SerializeObject(DateTime.Now.Add(warningPeriod)), cacheOptions);

                var timerPeriod = period.Add(TimeSpan.FromSeconds(1));
                var warningTimePeriod = warningPeriod.Add(TimeSpan.FromSeconds(1));

                _warningTimer = null;
                _warningTimerGCIssue = null;
                _sessionTimers.TryGetValue(warningKey, out _warningTimer);
                _logger.LogDebug($"warningTimer {_warningTimer}");
                if (_warningTimer == null)
                {
                    _logger.LogDebug("warningTimer is null");

                    _warningTimerGCIssue = new Timer(new TimerCallback(WarningCallback), (warningKey, turnContext), warningTimePeriod, warningTimePeriod);

                    _warningTimer = _sessionTimers.GetOrAdd(warningKey, _warningTimerGCIssue);
                }

                _timer = null;
                _timerGCIssue = null;
                _sessionTimers.TryGetValue(key, out _timer);
                _logger.LogDebug($"timer {_timer}");
                if (_timer == null)
                {
                    _logger.LogDebug("timer is null");
                    _logger.LogDebug($"key {key}");
                    _timerGCIssue = new Timer(new TimerCallback(Callback), (key, turnContext), timerPeriod, timerPeriod);
                    _timer = _sessionTimers.GetOrAdd(key, _timerGCIssue);
                }

                _warningTimer.Change(warningTimePeriod, warningTimePeriod);
                _logger.LogDebug("chatSessionTimeoutMiddleware timer change");
                _timer.Change(timerPeriod, timerPeriod);

            }
            _logger.LogDebug("chatSessionTimeoutMiddleware nextturn");
            await nextTurn(cancellationToken).ConfigureAwait(false);

        }

        private async void Callback(object target)
        {
            //telemetry.TrackEvent("ChatSessionMiddleware - InactivityCallback");
            _logger.LogDebug("ChatSessionMiddleware InactivityCallback");
            var tuple = ((string, ITurnContext))target;
            ObjectKey = tuple.Item1;
            var turnContext = tuple.Item2;

            foreach (var conversationReference in _conversationReferences.Values)
            {
                _logger.LogDebug("ChatSessionMiddleware InactivityCallback for loop");
                await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, EndOfChatCallback, default(CancellationToken));
            }

        }

        private async void WarningCallback(object target)
        {
            //telemetry.TrackEvent("ChatSessionMiddleware - WarningCallback");
            _logger.LogDebug("ChatSessionMiddleware WarningCallback");
            var tuple = ((string, ITurnContext))target;
            ObjectKey = tuple.Item1;
            var turnContext = tuple.Item2;

            foreach (var conversationReference in _conversationReferences.Values)
            {
                _logger.LogDebug("ChatSessionMiddleware WarningCallback for loop");
                await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, WarningMessageCallback, default(CancellationToken));
            }

        }


        private async Task WarningMessageCallback(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            //telemetry.TrackEvent("ChatSessionMiddleware - WarningMessageCallback");
            _logger.LogDebug("ChatSessionMiddleware WarningMessageCallback");
            var customConversationState = await _customConversationStateAccessors.CustomConversationState.GetAsync(turnContext, () => new CustomConversationState());

            void DisposeTimer()
            {
                bool found = _sessionTimers.TryRemove(ObjectKey, out var timer);
                if (found)
                {
                    timer.Dispose();
                    timer = null;
                }

            }

            var json = await _distributedCache.GetStringAsync(ObjectKey);
            var hasChatSessionRunning = await _sessionHandler.HasRunningSessionAsync(turnContext.Activity.Conversation.Id);

            if (hasChatSessionRunning)
            {
                DisposeTimer();
                return;
            }

            if (!string.IsNullOrEmpty(json))
            {
                var sessionEnd = JsonConvert.DeserializeObject<DateTime>(json);

                if (DateTime.Now >= sessionEnd)
                {
                    //telemetry.TrackEvent("ChatSessionMiddleware - SendingWarningMessage");
                    _logger.LogDebug("ChatSessionMiddleware SendingWarningMessage");
                    await _systemMessageSender.SendSystemMessage(turnContext, customConversationState, turnContext.Activity, ResourceIds.BotWarningEndOfChat);
                }
            }

            DisposeTimer();

        }

        private async Task EndOfChatCallback(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            //telemetry.TrackEvent("ChatSessionMiddleware - EndOfChatCallback");
            _logger.LogDebug("ChatSessionMiddleware EndOfChatCallback");
            var chatSdk = (IChatProvider)_serviceProvider.GetService(typeof(IChatProvider));
            var customConversationState = await _customConversationStateAccessors.CustomConversationState.GetAsync(turnContext, () => new CustomConversationState());

            void DisposeTimer()
            {
                bool found = _sessionTimers.TryRemove(ObjectKey, out var timer);
                if (found)
                {
                    timer.Dispose();
                    timer = null;
                }

            }

            var json = await _distributedCache.GetStringAsync(ObjectKey);
            var hasChatSessionRunning = await _sessionHandler.HasRunningSessionAsync(turnContext.Activity.Conversation.Id);

            if (hasChatSessionRunning)
            {
                DisposeTimer();
                return;
            }

            if (!string.IsNullOrEmpty(json))
            {
                var sessionEnd = JsonConvert.DeserializeObject<DateTime>(json);
                if (DateTime.Now >= sessionEnd)
                {
                    var parts = ObjectKey.Split(new char[] { ':' });
                    var dict = new Dictionary<string, string>
                    {
                        {"EndTime", json },
                        {"State", JsonConvert.SerializeObject(customConversationState) }
                    };

                    _telemetryManager.TrackEvent("AutomaticChatClosing", parts[parts.Length - 1], dict);

                    DisposeTimer();
                    //telemetry.TrackEvent("ChatSessionMiddleware - SendingEndOfChatMessage");
                    _logger.LogDebug("ChatSessionMiddleware SendingEndOfChatMessage");
                    await _systemMessageSender.SendSystemMessage(turnContext, customConversationState, turnContext.Activity, ResourceIds.BotAutomaticEndOfChat);
                    await Task.Delay(2000);
                    await chatSdk.EndChat(customConversationState.ChatContext, turnContext);
                }
            }
            else
            {
                DisposeTimer();
            }
        }


    }
}

代码产生问题/或未命中:

if (_warningTimer == null)
                {
                    _logger.LogDebug("warningTimer is null");

                    _warningTimerGCIssue = new Timer(new TimerCallback(WarningCallback), (warningKey, turnContext), warningTimePeriod, warningTimePeriod);

                    _warningTimer = _sessionTimers.GetOrAdd(warningKey, _warningTimerGCIssue);
                }

上述部分应在特定警告时间后调用 WarningCallback 函数,它在本地运行代码时会成功调用它,但不会在部署的环境中调用它。

C# 多线程 计时器 回调 聊天机器人

评论

0赞 Enigmativity 9/27/2022
你认为你可以将代码减少到一个点来证明这个问题吗?
0赞 Malik Tanzeel 9/27/2022
@Enigmativity指出了代码,造成了问题
0赞 Enigmativity 9/27/2022
你误解了我的意思。您能否编写最少量的代码来演示该问题并仅向我们展示?

答: 暂无答案