首页 > 解决方案 > 防止 Web 套接字使用者代码被执行两次

问题描述

想象一下,我们使用具有非常快速的财务数据的 Web 套接字。在高峰时期,网络套接字方法每秒被调用数百到数千次。

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

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;
}

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

标签: c#websocket

解决方案


这里的关键竞争条件似乎是检查_inExecution[key]和更新_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);
            }
        }
    }

您也可以使用 a Dictionary<string, bool>,但 aHashSet<string>在这里可以正常工作。ADictionary<string, bool>可以避免一些键空间开销,但是 - 只是操纵值 - 类似于:

    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工作得很好。


推荐阅读