Why is it threadunsafe to do an await inside a .ForEach() ?
In a .Net Core 3.1 project I'm Selecting from some WebApi, the list of users matching a criteria, then I delete them, and finally I ask for the list of users again. Naturally, this time I expect the list to be empty.
The erroneous code
var existing = (await client.GetByMatchAsync(new SearchParameters() {..})).ToList();
existing.ForEach(async x => await client.DeleteByIdAsync(x.Id));
var ensure = (await client.GetByMatchAsync(new SearchParameters() {..})).ToList();
ensure.Count.Should().Be(0); <-- ERROR WAS 1!
I've found that when I insert a Thread.Sleep(50) before var ensure the code works. This clearly indicates to me that there are threading issues going on I don't understand.
the working code (using a delay)
var existing = (await client.GetByMatchAsync(new SearchParameters() {..})).ToList();
existing.ForEach(async x => await client.DeleteByIdAsync(x.Id));
Thread.Sleep(50);
var ensure = (await client.GetByMatchAsync(new SearchParameters() {..})).ToList();
ensure.Count.Should().Be(0);
Alternative Working code (using foreach)
var existing = (await client.GetByMatchAsync(new SearchParameters() {..})).ToList();
foreach (var x in existing)
{
await client.DeleteByIdAsync(x.Id);
}
var ensure = (await client.GetByMatchAsync(new SearchParameters() {..})).ToList();
ensure.Count.Should().Be(0);
decompiling the .ForEach() I can't see where the threading problem occur
public void ForEach(Action<T> action)
{
if (action == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.action);
}
int version = _version;
for (int i = 0; i < _size; i++)
{
if (version != _version)
{
break;
}
action!(_items[i]);
}
if (version != _version)
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
}