.netcore入门17:IChangeToken和ChangeToken用法简介

环境:

  • .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()
                {
                }
            }
        }
    }
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页