【问题标题】:Prevent web socket consumer code from being executed twice防止 Web 套接字使用者代码被执行两次
【发布时间】:2019-10-21 08:57:35
【问题描述】:

假设我们使用具有真正高速财务数据的网络套接字。在高峰期,网络套接字方法每秒被调用数百到数千次。

我们的网络套接字方法中有一个条件不时变为真。在这种情况下,应该调用另一个方法。但只有一次。由于该方法的执行速度,很难防止重复执行。代码如下所示:

private readonly ConcurrentDictionary<string, bool> _inExecution = new ConcurrentDictionary<string, bool>();

private void SubscribeToSocket()
{
    _socket.Connect();

    var subscription = SocketSubscriptions.CreateSubsription(data =>
    {
        Task.Run(async () =>
        {
            // read data

            if (condition)
            {
                // call method only once
                await Execute(key);

                condition = false;
            }
        }
    }
}

private async Task Execute(string key)
{
    // Even with this statement the code calls are too fast and sometimes gets executed twice
    if (!_inExecution[key])
    {
        _inExecution[key] = true;

        // do something..
    }
}

我已经尝试在 Execute() 方法之前通过随机等待来防止双重执行。像这样:

if (condition)
{
    var rnd = new Random();
    await Task.Delay(rnd.Next(15, 115));

    // call method only once
    await Execute(key);

    condition = false;
}

但即使在某些特殊情况下也会执行两次。有没有更好的方法来防止这种情况发生?

【问题讨论】:

  • 使用互斥体?或其他并发管理工具:docs.microsoft.com/en-us/dotnet/api/…
  • 看起来,正如 Milney 所建议的,您需要某种形式的锁定。试试SemaphoreSlimstackoverflow.com/a/45769160/1481699
  • 注意:SemaphoreSlim 在同步和异步代码的边缘情况下会出现一些讨厌的故障...我有一个 MutexSlim 专门设计用于避免它们,如果它可能会有所帮助......但是,这是一个尖叫某种信号量/互斥体设置的场景
  • @MarcGravell 当然,一切都会有所帮助。您是否有指向其他信息的链接或带有示例代码的 github 存储库?

标签: c# websocket


【解决方案1】:

这里的关键竞争条件似乎是检查 _inExecution[key] 和 *updating_inExecution[key] = true' 之间的竞争;多个呼叫者可以通过那里。有多种方法可以使它变得健壮,但是在你的情况下,经过考虑,我很确定最简单的方法是简单地在集合上进行同步,即

    private readonly HashSet<string> _inExecution = new HashSet<string>();
    private async Task Execute(string key)
    {
        // Even with this statement the code calls are too fast and sometimes gets executed twice
        bool haveLock = false;
        try
        {
            lock(_inExecution) { haveLock = _inExecution.Add(key); }
            if (haveLock)
            {
                // ... your code here
            }
        }
        finally
        {
            if (haveLock)
            {
                lock (_inExecution) _inExecution.Remove(key);
            }
        }
    }

您也可以使用Dictionary&lt;string, bool&gt;,但HashSet&lt;string&gt; 在这里可以正常工作。 Dictionary&lt;string, bool&gt; 可以避免一些键空间开销,不过 - 只是操纵值 - 类似于:

    private readonly Dictionary<string, bool> _inExecution = new Dictionary<string, bool>();
    private async Task Execute(string key)
    {
        // Even with this statement the code calls are too fast and sometimes gets executed twice
        bool haveLock = false;
        try
        {
            lock(_inExecution)
            {
                if (!_inExecution.TryGetValue(key, out var state) || !state)
                {   // if missing entirely, or not currently held: take it
                    haveLock = _inExecution[key] = true;
                }
            }
            if (haveLock)
            {
                // ... your code here
            }
        }
        finally
        {
            if (haveLock)
            {
                lock (_inExecution) _inExecution[key] = false;
            }
        }
    }

需要注意的重要一点是,您不要将 lock 保留在实际的 // ... your code here 位之上 - 这会阻止所有并发执行,这不是您想要的。

如果您想整理一下,可以使用自定义一次性用品等来构建它,但 try/finally 可以正常工作。

【讨论】:

  • 与 HashSet 配合得很好。谢谢,马克!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-23
  • 1970-01-01
相关资源
最近更新 更多