【发布时间】:2020-08-06 20:08:33
【问题描述】:
为什么在.ForEach() 中执行await 是线程不安全的?
在一个 .Net Core 3.1 项目中,我从一些 WebApi 中选择符合条件的用户列表,然后删除它们,最后我再次询问用户列表。当然,这一次我希望列表是空的。
错误代码
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!
我发现当我在var ensure 之前插入Thread.Sleep(50) 时,代码有效。这清楚地向我表明存在我不理解的线程问题。
工作代码(使用延迟)
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);
替代工作代码(使用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);
反编译.ForEach() 看不到线程问题出现在哪里
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();
}
【问题讨论】:
-
.ForEach() 只是采取行动,而不是 Func
,因此您永远不会等待它。 -
existing.ForEach启动一堆任务,而不是等待它们完成,所以它们都在同时运行。foreach {await}在开始下一个任务之前等待上一个任务。 -
在 ForEach 反编译中,action!(_items[i]);是一个奇怪的说法 感叹号是什么?
-
@CRice 问 ReSharper
!是什么意思 ;) -
这个问题被描述为#5 here
标签: c# linq async-await