我要在更改配置时记录日志。
I do this在Program.cs
或Startup.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毫秒。因此,反跳对我不起作用,只会在报告的更改之间造成延迟。如果我设置一个很高的值,那么我只会得到一份报告,但这并不理想(并且掩盖了问题)。
所以我想知道:
答案 0 :(得分:1)
讨论 here 的多个更改检测问题(以及多个存储库中的至少十几个其他问题)是他们拒绝使用内置机制解决的问题。
MS 文档使用 file hashing 方法,但我认为去抖动更好。
我的解决方案使用 async(避免 async-in-sync 可能会意外炸毁某些东西)和一个 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
答案 1 :(得分:0)
希望其他人可以回答您的问题,但是我确实遇到了这个问题并找到了Gist by cocowalla。
cocowalla提供的代码会反跳,而不仅仅是等待。它为我成功地删除了更改回调。
Cocowalla还包含一个扩展方法,因此您只需在OnChange
上调用IConfiguration
。
这里是一个示例:
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毫秒左右停止重复数据消除。