【问题标题】:Log configuration changes in ASP.NET Core记录 ASP.NET Core 中的配置更改
【发布时间】:2019-02-02 20:32:23
【问题描述】:

我想在配置更改时记录。

I do thisProgram.csStartup.cs

ChangeToken.OnChange(
  () => configuration.GetReloadToken(),
  state => logger.Information("Configuration reloaded"),
  (object)null
);

但我收到双重更改报告,因此需要对其进行去抖动处理。 The advice is to do this:

ChangeToken.OnChange(
  () => configuration.GetReloadToken(),
  state => { Thread.Sleep(2000); logger.Information("Configuration reloaded"); },
  (object)null
);

我在这里使用2000,因为我不确定什么是合理的值。

我发现有时我仍然会得到多个更改检测,间隔 2000 毫秒。所以去抖动对我不起作用,只会导致报告的更改之间出现延迟。如果我设置了一个较高的值,那么我只会得到一份报告,但这并不理想(并且掩盖了问题)。

所以我想知道:

  • 这真的是去抖动,还是只是将报告的更改排队?
  • 我使用了从 1000 到 5000 的值来获得不同程度的成功。其他人在用什么?
  • 是否向服务器的主线程发出休眠?我希望不会!

【问题讨论】:

    标签: c# asp.net-core


    【解决方案1】:

    希望其他人可以回答您的问题,但我确实遇到了这个问题并找到了这个Gist by cocowalla。 cocowalla 提供的代码会去抖动而不是等待。它成功地为我删除了更改回调。 Cocowalla 还包含一个扩展方法,因此您只需在 IConfiguration 上调用 OnChange

    这是一个示例:

    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Primitives;
    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    
    class Program
    {
        public static async Task Main(string[] args)
        {
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
                .Build();
    
            configuration.OnChange(() => Console.WriteLine("configuration changed"));
    
            while (true)
            {
                await Task.Delay(1000);
            }
        }
    }
    
    public class Debouncer : IDisposable
    {
        private readonly CancellationTokenSource cts = new CancellationTokenSource();
        private readonly TimeSpan waitTime;
        private int counter;
    
        public Debouncer(TimeSpan? waitTime = null)
        {
            this.waitTime = waitTime ?? TimeSpan.FromSeconds(3);
        }
    
        public void Debouce(Action action)
        {
            var current = Interlocked.Increment(ref this.counter);
    
            Task.Delay(this.waitTime).ContinueWith(task =>
            {
                    // Is this the last task that was queued?
                    if (current == this.counter && !this.cts.IsCancellationRequested)
                    action();
    
                task.Dispose();
            }, this.cts.Token);
        }
    
        public void Dispose()
        {
            this.cts.Cancel();
        }
    }
    
    public static class IConfigurationExtensions
    {
        /// <summary>
        /// Perform an action when configuration changes. Note this requires config sources to be added with
        /// `reloadOnChange` enabled
        /// </summary>
        /// <param name="config">Configuration to watch for changes</param>
        /// <param name="action">Action to perform when <paramref name="config"/> is changed</param>
        public static void OnChange(this IConfiguration config, Action action)
        {
            // IConfiguration's change detection is based on FileSystemWatcher, which will fire multiple change
            // events for each change - Microsoft's code is buggy in that it doesn't bother to debounce/dedupe
            // https://github.com/aspnet/AspNetCore/issues/2542
            var debouncer = new Debouncer(TimeSpan.FromSeconds(3));
    
            ChangeToken.OnChange<object>(config.GetReloadToken, _ => debouncer.Debouce(action), null);
        }
    }
    

    在示例中,去抖动延迟为 3 秒,对于我的小 json 文件,去抖动延迟在 230 毫秒左右停止重复数据删除。

    【讨论】:

      【解决方案2】:

      here 讨论的多个更改检测问题(以及多个存储库中的至少十几个其他问题)是他们拒绝使用内置机制解决的问题。

      MS 文档使用file hashing 方法,但我认为去抖动效果更好。

      我的解决方案使用异步(避免异步同步,这可能会意外炸毁某些东西)和一个 hosted service 来消除更改检测。

      Debouncer.cs:

      public sealed class Debouncer : IDisposable {
      
        public Debouncer(TimeSpan? delay) => _delay = delay ?? TimeSpan.FromSeconds(2);
      
        private readonly TimeSpan _delay;
        private CancellationTokenSource? previousCancellationToken = null;
      
        public async Task Debounce(Action action) {
          _ = action ?? throw new ArgumentNullException(nameof(action));
          Cancel();
          previousCancellationToken = new CancellationTokenSource();
          try {
            await Task.Delay(_delay, previousCancellationToken.Token);
            await Task.Run(action, previousCancellationToken.Token);
          }
          catch (TaskCanceledException) { }    // can swallow exception as nothing more to do if task cancelled
        }
      
        public void Cancel() {
          if (previousCancellationToken != null) {
            previousCancellationToken.Cancel();
            previousCancellationToken.Dispose();
          }
        }
      
        public void Dispose() => Cancel();
      
      }
      

      ConfigWatcher.cs:

      public sealed class ConfigWatcher : IHostedService, IDisposable {
      
        public ConfigWatcher(IServiceScopeFactory scopeFactory, ILogger<ConfigWatcher> logger) {
          _scopeFactory = scopeFactory;
          _logger = logger;
        }
      
        private readonly IServiceScopeFactory _scopeFactory;
        private readonly ILogger<ConfigWatcher> _logger;
      
        private readonly Debouncer _debouncer = new(TimeSpan.FromSeconds(2));
      
        private void OnConfigurationReloaded() {
          _logger.LogInformation("Configuration reloaded");
          // ... can do more stuff here, e.g. validate config
        }
      
        public Task StartAsync(CancellationToken cancellationToken) {
          ChangeToken.OnChange(
            () => {                                                 // resolve config from scope rather than ctor injection, in case it changes (this hosted service is a singleton)
              using var scope = _scopeFactory.CreateScope();
              var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
              return configuration.GetReloadToken();
            },
            async () => await _debouncer.Debounce(OnConfigurationReloaded)
          );
          return Task.CompletedTask;
        }
      
        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
      
        public void Dispose() => _debouncer.Dispose();
      
      }
      

      Startup.cs:

      services.AddHostedService<ConfigWatcher>();        // registered as singleton
      

      【讨论】:

        猜你喜欢
        • 2018-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-11
        • 2021-12-06
        • 1970-01-01
        相关资源
        最近更新 更多