如何将突发事件缓冲成更少的结果动作

Guy*_*Guy 4 c# async-await

我想将多个事件减少为一个延迟动作。在发生一些触发之后,我希望还会有更多类似的触发,但是我不希望重复出现延迟的动作。动作等待,让爆发有机会完成。
问题:如何以一种可重复使用的优雅方式进行操作?
到目前为止,我使用一个属性来标记事件并触发如下所示的延迟动作:

public  void SomeMethod()
    {
        SomeFlag = true; //this will intentionally return to the caller before completing the resulting buffered actions.
    }
    private bool someFlag;
    public bool SomeFlag
    {
        get { return someFlag; }
        set
        {
            if (someFlag != value)
            {
                someFlag = value;
                if (value)
                    SomeDelayedMethod(5000);
            }
        }
    }

    public async void SomeDelayedMethod(int delay)
    {
        //some bufferred work.
        await Task.Delay(delay);
        SomeFlag = false;
    }
Run Code Online (Sandbox Code Playgroud)

下面是一种较短的方法,但是仍然不是通用的或可重用的...我想要一些简洁的方法来打包动作和标志,并保留功能(在执行完成之前返回调用方(如今天))。 我还需要能够将对象引用传递给此操作)

 public void SerializeAccountsToConfig()
    {
        if (!alreadyFlagged)
        {
            alreadyFlagged = true;
            SerializeDelayed(5000, Serialize);
        }
    }
    public async void SerializeDelayed(int delay, Action whatToDo)
    {
        await Task.Delay(delay);
        whatToDo();
    }

    private bool alreadyFlagged;
    private void Serialize()
    {
        //some buferred work.
        //string json = JsonConvert.SerializeObject(Accounts, Formatting.Indented);
        //Settings1.Default.Accounts = json;
        //Settings1.Default.Save();
        alreadyFlagged = false;
    }
Run Code Online (Sandbox Code Playgroud)

And*_*erd 5

这是一个线程安全且可重用的解决方案。

您可以创建的实例DelayedSingleAction,然后在构造函数中传递要执行的操作。我认为这是线程安全的,尽管存在很小的风险,即在开始执行操作之前它将重新启动计时器,但是我认为无论解决方案是什么,这种风险都会存在。

public class DelayedSingleAction
{
    private readonly Action _action;
    private readonly long _millisecondsDelay;
    private long _syncValue = 1;
    public DelayedSingleAction(Action action, long millisecondsDelay)
    {
        _action = action;
        _millisecondsDelay = millisecondsDelay;
    }

    private Task _waitingTask = null;
    private void DoActionAndClearTask(Task _)
    {
        Interlocked.Exchange(ref _syncValue, 1);
        _action();
    }

    public void PerformAction()
    {
        if (Interlocked.Exchange(ref _syncValue, 0) == 1)
        {
            _waitingTask = Task.Delay(TimeSpan.FromMilliseconds(_millisecondsDelay))
                               .ContinueWith(DoActionAndClearTask);
        }
    }

    public Task Complete()
    {
        return _waitingTask ?? Task.FromResult(0);
    }
}
Run Code Online (Sandbox Code Playgroud)

请参阅此dotnetfiddle中的示例,该示例从多个线程连续调用一个动作。

https://dotnetfiddle.net/el14wZ


Guy*_*Guy 1

基于安德鲁提出的解决方案,这里有一个更通用的解决方案。
延迟动作的声明和实例创建:

public DelayedSingleAction<Account> SendMailD;
Run Code Online (Sandbox Code Playgroud)

在函数内或构造函数中创建实例(这可以是此类操作的集合,每个操作都作用于不同的对象):

SendMailD = new DelayedSingleAction<Account>(SendMail, AccountRef, 5000);
Run Code Online (Sandbox Code Playgroud)

重复调用此操作

SendMailD.PerformAction();
Run Code Online (Sandbox Code Playgroud)

发送邮件是你会“爆控”的动作。其签名匹配:

public int SendMail(Account A)
    {}
Run Code Online (Sandbox Code Playgroud)

这是更新后的类

    public class DelayedSingleAction<T>
{
    private readonly Func<T, int> actionOnObj;
    private T tInstance;
    private readonly long millisecondsDelay;
    private long _syncValue = 1;

    public DelayedSingleAction(Func<T, int> ActionOnObj, T TInstance, long MillisecondsDelay)
    {
        actionOnObj = ActionOnObj;
        tInstance = TInstance;
        millisecondsDelay = MillisecondsDelay;
    }

    private Task _waitingTask = null;

    private void DoActionAndClearTask(Task _)
    {
        Console.WriteLine(string.Format("{0:h:mm:ss.fff} DelayedSingleAction Resetting SyncObject: Thread {1} for {2}", DateTime.Now, System.Threading.Thread.CurrentThread.ManagedThreadId, tInstance));
        Interlocked.Exchange(ref _syncValue, 1);
        actionOnObj(tInstance);
    }

    public void PerformAction()
    {
        if (Interlocked.Exchange(ref _syncValue, 0) == 1)
        {
            Console.WriteLine(string.Format("{0:h:mm:ss.fff} DelayedSingleAction Starting the timer: Thread {1} for {2}", DateTime.Now, System.Threading.Thread.CurrentThread.ManagedThreadId, tInstance));
            _waitingTask = Task.Delay(TimeSpan.FromMilliseconds(millisecondsDelay)).ContinueWith(DoActionAndClearTask);
        }
    }

    public Task Complete()
    {
        return _waitingTask ?? Task.FromResult(0);
    }
}
Run Code Online (Sandbox Code Playgroud)