确保委托只执行一次

本文关键字:一次 执行 确保 | 更新日期: 2023-09-27 18:12:03

我有这个方法可以用作委托。由于某些原因,我希望它在每次事件触发时只做一些事情。

//In the real code those lines are in very different places
btn.Click += foo.DoOnlyOnce;
btn.Click += foo.DoOnlyOnce;

将导致DoStuff()只执行一次

 private List<EventArgs> _eventArgsList = new List<EventArgs>();
 public void DoOnlyOnce(object sender, EventArgs e)
 {            
     if (!_eventArgsList.Contains(e))
     {
          _eventArgsList.Add(e);
          DoStuff();
     }            
 }

如果btn被点击两次,它仍然按照预期执行两次DoStuff()。

这个解决方案的一个问题是_eventArgsList,它将无限期地跟踪eventArgs。由于GC不会像处理所有订阅后那样处理内存泄漏,因此会产生内存泄漏。

我能在委托中获得原始事件吗?

例如

GetOriginalEvent().GetInvocationList()

可以帮我决定什么时候清除这个列表。

或者,我可以用更好的方法解决这个问题吗?我最初开始尝试这样做:

var handler = new DoEventOnlyOnce(btn.Click);
handler.DoOnlyOnce += DoStuff;
handler.DoOnlyOnce += DoStuff;

所以你可以有一个原始事件的引用。但没能成功。

你可能会说我应该一直做

btn.Click -= foo.DoOnlyOnce;
btn.Click += foo.DoOnlyOnce;

但是我想要这个逻辑在DoOnlyOnce,所以我不需要依赖于正确的用法

确保委托只执行一次

如何使用一个简单的布尔开关:

private bool _Executed;
private void OnButtonClick(object sender, EventArgs e)
{
    if(!_Executed)
    {
        _Executed = true;
        DoOnlyOnce();
    }    
}
private void DoOnlyOnce()
{
    Console.WriteLine("Watch carefully. You'll never see me again.");
}

我把逻辑放在事件处理程序和DoOnlyOnce()方法之外。但我认为这只是一个品味问题(或您实际代码中的其他逻辑),如果您希望它在该方法内或在调用代码内。

好的,在得到评论和重读你的问题之后,让我们做另一种方法。我们创建一个缓存,在这里我们可以记住谁正在运行(sender和我们获得Task的距离)。这样就可以解决这个问题了:

private Dictionary<Object, Task> _Workers = new Dictionary<object, Task>();
private void OnButton1Click(object sender, EventArgs e)
{
    StartIfNotBusy(sender);
}
private void OnButton2Click(object sender, EventArgs e)
{
    StartIfNotBusy(sender);
}
private void StartIfNotBusy(object sender)
{
    Task worker;
    if (_Workers.TryGetValue(sender, out worker)
        && !worker.IsCompleted)
    {
        Console.WriteLine("Sorry, I'm currently busy.");
        return;
    }
    _Workers[sender] = DoSomething();
}
private Task DoSomething()
{
    return Task.Delay(1000).ContinueWith(_ => Console.WriteLine("Hey, I finished working."));
}