Na documentação do ConcurrentDictionary é declarado que a fábrica de valores é chamada fora dos bloqueios e que uma chave/valor pode ser inserido por outro thread enquanto valueFactory está gerando um valor. Você não pode confiar que só porque valueFactory foi executado, seu valor produzido será inserido no dicionário e retornado.
Meu código original se parece com isso:
public class MyClass
{
private readonly ConcurrentDictionary<string, Task<string>> myData = new();
public Task<string> GetData(string key)
{
return myData.GetOrAdd(key, key => longCalculation(key));
}
}
Se várias threads chamarem MyClass.GetData("theSameKey")
ao mesmo tempo, longCalculation
"theSameKey" será executado várias vezes porque valueFactory
será delegado a outra string.
Para evitar ter que longCalculation
executar desnecessariamente, alterei meu código para usar Lazy, conforme sugerido por JG no SD, para que o delegado fosse executado apenas uma vez:
public class MyClass
{
private readonly ConcurrentDictionary<string, Lazy<Task<string>>> myData = new();
public async Task<string> GetData(string key)
{
var lazyTask = new new Lazy<Task<string>>(() => longCalculation(key),
LazyThreadSafetyMode.ExecutionAndPublication);
var task = myData.GetOrAdd(key, lazyTask).Value;
return await task;
}
}
Esta versão, se houver várias chamadas de threads, MyClass.GetData("theSameKey")
longCalculation
é executada apenas uma vez. Embora eu tenha uma correção para obter o comportamento desejado, gostaria de ter mais detalhes explicando por que isso acontece.