环境:
- .netcore 3.1.1.0
- vs2019 16.4.5
参照:
浅析 .Net Core中Json配置的自动更新
.net core 常见设计模式-IChangeToken
IChangeToken:
这个接口代表一个事件变更的令牌,你从监测者那里拿到令牌后就可以向这个令牌注入变更事件,这样当监测到动态后就会执行你注册的事件,需要注意的是这个令牌只能使用一次。它的源码如下:
using System;
namespace Microsoft.Extensions.Primitives
{
public interface IChangeToken
{
bool HasChanged { get; }
bool ActiveChangeCallbacks { get; }
IDisposable RegisterChangeCallback(Action<object> callback, object state);
}
}
那么我们从哪里获取这个令牌呢?
我们可以从PhysicalFileProvider
中获取令牌,而PhysicalFileProvider
正好可以监测文件夹的变化。
实验一、一次性监测c:\temp文件夹的变化
看如下的代码示例:
static void Main(string[] args)
{
Console.WriteLine("开始监测文件夹 c:\\temp");
var phyFileProvider = new PhysicalFileProvider("c:\\temp");
IChangeToken changeToken = phyFileProvider.Watch("*.*");
changeToken.RegisterChangeCallback(_ =>
{
Console.WriteLine("检测到文件夹有变化!" + _);
}, "xiaoming");
Console.ReadLine();
}
实验效果如下图所示,可以看到:虽然我们新建了两个文件夹,但是只监测到了一次。
那么如果想一直监测呢?
实验二、一直监测文件夹c:\temp的变化
请看下面的代码:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("开始监测文件夹 c:\\temp");
MyChangeToken.OnChange<string>(() =>
{
var phyFileProvider = new PhysicalFileProvider("c:\\temp");
IChangeToken changeToken = phyFileProvider.Watch("*.*");
return changeToken;
},
_ =>
{
Console.WriteLine("检测到文件夹有变化!" + _);
}, "xiaoming");
Console.ReadLine();
}
}
public static class MyChangeToken
{
public static void OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
{
var token = changeTokenProducer();
token.RegisterChangeCallback(_ =>
{
changeTokenConsumer();
OnChange(changeTokenProducer, changeTokenConsumer);
}, new object());
}
public static void OnChange<TState>(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state)
{
var token = changeTokenProducer();
token.RegisterChangeCallback(_ =>
{
changeTokenConsumer(state);
OnChange(changeTokenProducer, changeTokenConsumer, state);
}, state);
}
}
这样就会发现它会一直监测改变,效果如下:
总结:
其实微软已经提供了类似MyChangeToken的功能,它就是ChangeToken
,看它的源码如下:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Threading;
namespace Microsoft.Extensions.Primitives
{
/// <summary>
/// Propagates notifications that a change has occurred.
/// </summary>
public static class ChangeToken
{
/// <summary>
/// Registers the <paramref name="changeTokenConsumer"/> action to be called whenever the token produced changes.
/// </summary>
/// <param name="changeTokenProducer">Produces the change token.</param>
/// <param name="changeTokenConsumer">Action called when the token changes.</param>
/// <returns></returns>
public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
{
if (changeTokenProducer == null)
{
throw new ArgumentNullException(nameof(changeTokenProducer));
}
if (changeTokenConsumer == null)
{
throw new ArgumentNullException(nameof(changeTokenConsumer));
}
return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
}
/// <summary>
/// Registers the <paramref name="changeTokenConsumer"/> action to be called whenever the token produced changes.
/// </summary>
/// <param name="changeTokenProducer">Produces the change token.</param>
/// <param name="changeTokenConsumer">Action called when the token changes.</param>
/// <param name="state">state for the consumer.</param>
/// <returns></returns>
public static IDisposable OnChange<TState>(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state)
{
if (changeTokenProducer == null)
{
throw new ArgumentNullException(nameof(changeTokenProducer));
}
if (changeTokenConsumer == null)
{
throw new ArgumentNullException(nameof(changeTokenConsumer));
}
return new ChangeTokenRegistration<TState>(changeTokenProducer, changeTokenConsumer, state);
}
private class ChangeTokenRegistration<TState> : IDisposable
{
private readonly Func<IChangeToken> _changeTokenProducer;
private readonly Action<TState> _changeTokenConsumer;
private readonly TState _state;
private IDisposable _disposable;
private static readonly NoopDisposable _disposedSentinel = new NoopDisposable();
public ChangeTokenRegistration(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state)
{
_changeTokenProducer = changeTokenProducer;
_changeTokenConsumer = changeTokenConsumer;
_state = state;
var token = changeTokenProducer();
RegisterChangeTokenCallback(token);
}
private void OnChangeTokenFired()
{
// The order here is important. We need to take the token and then apply our changes BEFORE
// registering. This prevents us from possible having two change updates to process concurrently.
//
// If the token changes after we take the token, then we'll process the update immediately upon
// registering the callback.
var token = _changeTokenProducer();
try
{
_changeTokenConsumer(_state);
}
finally
{
// We always want to ensure the callback is registered
RegisterChangeTokenCallback(token);
}
}
private void RegisterChangeTokenCallback(IChangeToken token)
{
var registraton = token.RegisterChangeCallback(s => ((ChangeTokenRegistration<TState>)s).OnChangeTokenFired(), this);
SetDisposable(registraton);
}
private void SetDisposable(IDisposable disposable)
{
// We don't want to transition from _disposedSentinel => anything since it's terminal
// but we want to allow going from previously assigned disposable, to another
// disposable.
var current = Volatile.Read(ref _disposable);
// If Dispose was called, then immediately dispose the disposable
if (current == _disposedSentinel)
{
disposable.Dispose();
return;
}
// Otherwise, try to update the disposable
var previous = Interlocked.CompareExchange(ref _disposable, disposable, current);
if (previous == _disposedSentinel)
{
// The subscription was disposed so we dispose immediately and return
disposable.Dispose();
}
else if (previous == current)
{
// We successfuly assigned the _disposable field to disposable
}
else
{
// Sets can never overlap with other SetDisposable calls so we should never get into this situation
throw new InvalidOperationException("Somebody else set the _disposable field");
}
}
public void Dispose()
{
// If the previous value is disposable then dispose it, otherwise,
// now we've set the disposed sentinel
Interlocked.Exchange(ref _disposable, _disposedSentinel).Dispose();
}
private class NoopDisposable : IDisposable
{
public void Dispose()
{
}
}
}
}
}