想象一下,我们消耗了一个具有确实个快速财务数据的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;
}
但是在某些特殊情况下,即使执行了两次。有更好的方法来防止这种情况吗?
答案 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
可以正常工作。