Условие ключевой гонки здесь представляется гонкой между проверкой _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
отлично работает.