Почему мой баунсер + перехватчик работает несколько раз? - PullRequest
2 голосов
/ 23 апреля 2020

Я хочу использовать перехватчик Castle DynamicProxy для отмены уведомлений об изменениях свойств. Это работает, вроде. Событие изменения перехватывается, но вместо того, чтобы генерировать событие один раз, оно вращается и генерирует событие снова и снова с интервалом в одну секунду.

Оно никогда не выдает «OnPropertyChanged: Name». invocation.Proceed() похоже, не работает перехваченный метод? Почему он постоянно вызывает метод debouncer ? Как заставить его вызывать перехваченный метод , один раз?

Чтобы самостоятельно проверить этот код, создайте новый. Net Core 3 проект и добавьте ссылку на Castle.Core 4.4 +0,0. Или используйте это DotNetFiddle

Ожидаемый результат:

Создание inteceptor
Экземпляр не существует в WeakDebouncers. Добавление.
Свойство не существует в экземплярах debouncers. Добавление.
Создание debouncer
Активизирующее действие
Отклонено: User.OnPropertyChanged (Имя)

Фактический вывод:

Создание inteceptor
Экземпляр в WeakDebouncers не существует. Добавление.
Свойство не существует в экземплярах debouncers. Добавление.
Создание debouncer
Вызов действия
Debounce: User.OnPropertyChanged (Name)
Экземпляр существует в WeakDebouncers
Свойство существует в экземплярах debouncers
Активизирующее действие
Debounce: User.OnPropertyChanged (Name)
Экземпляр существует в WeakDebouncers
Свойство существует в экземплярах debouncers
Invoking action
Debounce: User.OnPropertyChanged (Name)

internal static class Program
{
    private static int Main()
    {
        var u = new User();
        var interceptor = new PropertyChangedDebounceInterceptor();
        var proxy = (User)new ProxyGenerator().CreateClassProxyWithTarget(
            typeof(User), 
            u, 
            interceptor);

        proxy.Name = "foo";
        Console.ReadLine();
        return 0;
    }
}

Перехватчик:

public class PropertyChangedDebounceInterceptor : IInterceptor
{        
    private static readonly ConditionalWeakTable<object, Dictionary<string, Debouncer>> WeakDebouncers 
        = new ConditionalWeakTable<object, Dictionary<string, Debouncer>>();

    public PropertyChangedDebounceInterceptor()
    {
        Console.WriteLine("Creating interceptor");
    }

    public void Intercept(IInvocation invocation)
    {
        var propertyName = (string)invocation.Arguments[0] ?? "";

        var debouncer = GetDebouncer(
            invocation.GetConcreteMethodInvocationTarget(), 
            propertyName);

        debouncer.Debouce(() =>
        {
            Console.WriteLine($"Debounced: {invocation.TargetType.Name}.OnPropertyChanged({propertyName})");
            invocation.Proceed();
        });
    }

    private Debouncer GetDebouncer(object obj, string propertyName)
    {
        Dictionary<string, Debouncer> dict;
        lock (WeakDebouncers)
        {
            if (!WeakDebouncers.TryGetValue(obj, out dict))
            {
                Console.WriteLine("Instance does not exist in WeakDebouncers.  Adding.");
                WeakDebouncers.Add(obj, dict = new Dictionary<string, Debouncer>());
            }
            else
            {
                Console.WriteLine("Instance exists in WeakDebouncers");
            }
        }

        lock (dict)
        {
            if (!dict.TryGetValue(propertyName, out var debouncer))
            {
                Console.WriteLine("Property does not exist in instance debouncers.  Adding.");
                dict.Add(propertyName, debouncer = new Debouncer(TimeSpan.FromSeconds(1)));
            }
            else
            {
                Console.WriteLine("Property exists in instance debouncers");
            }

            return debouncer;
        }
    }
}

Debouncer: На основе: https://gist.github.com/cocowalla/5d181b82b9a986c6761585000901d1b8

public class Debouncer : IDisposable
{
    private readonly CancellationTokenSource _cts = new CancellationTokenSource();
    private readonly TimeSpan _waitTime;
    private int _counter;

    public Debouncer(TimeSpan? waitTime = null)
    {
        Console.WriteLine("Creating debouncer");
        _waitTime = waitTime ?? TimeSpan.FromSeconds(3);
    }

    public void Debouce(Action action)
    {
        var current = Interlocked.Increment(ref _counter);

        Task.Delay(_waitTime).ContinueWith(task =>
        {
            if (current == _counter && !_cts.IsCancellationRequested)
            {
                Console.WriteLine("Invoking action");
                action();
            }

            task.Dispose();
        }, _cts.Token);
    }

    public void Dispose()
    {
        _cts.Cancel();
    }
}

Пользователь

public class User : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            if (value != _name)
            {
                _name = value;
                OnPropertyChanged(nameof(Name));
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        Console.WriteLine($"OnPropertyChanged: {propertyName}");
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...