阻止Web套接字使用者代码执行两次

时间:2019-06-05 20:18:25

标签: c# websocket

想象一下,我们消耗了一个具有确实个快速财务数据的Web套接字。在高峰时期,Web套接字方法每秒被称为数百至数千次。

我们的Web套接字方法中存在一个条件,该条件有时会变为true。在这种情况下,应调用另一种方法。但是只有一次。由于该方法的执行速度快,因此很难防止重复执行。代码如下:

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

但是在某些特殊情况下,即使执行了两次。有更好的方法来防止这种情况吗?

1 个答案:

答案 0 :(得分:1)

这里的关键竞争条件似乎是检查_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);
            }
        }
    }

您也可以使用Dictionary<string, bool>,但是HashSet<string>在这里工作正常。 Dictionary<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可以正常工作。