C# Chained ContinueWith 不等待上一个任务完成
C# Chained ContinueWith Not Waiting for Previous Task to Complete
我正在测试 C# 的异步性 async/await 并意外发现 ContinueWith 的后续代码不等待上一个任务完成:
public async Task<int> SampleAsyncMethodAsync(int number,string id)
{
Console.WriteLine($"Started work for {id}.{number}");
ConcurrentBag<int> abc = new ConcurrentBag<int>();
await Task.Run(() => { for (int count = 0; count < 30; count++) { Console.WriteLine($"[{id}] Run: {number}"); abc.Add(count); } });
Console.WriteLine($"Completed work for {id}.{number}");
return abc.Sum();
}
使用以下测试方法执行:
[Test]
public void TestAsyncWaitForPreviousTask()
{
for (int count = 0; count < 3; count++)
{
int scopeCount = count;
var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
.ContinueWith((prevTask) =>
{
return SampleAsyncMethodAsync(1, scopeCount.ToString());
})
.ContinueWith((prevTask2) =>
{
return SampleAsyncMethodAsync(2, scopeCount.ToString());
});
}
}
输出显示运行 0.0、1.0 和 2.0 的执行异步正确执行,但随后的 x.1 和 x.2 几乎立即开始并且 x.2 实际上在 x.1 之前完成。例如。如下所示:
[2] Run: 0
[2] Run: 0
[2] Run: 0
Completed work for 2.0
Started work for 0.1
Started work for 0.2 <-- surprise!
[0] Run: 2
[0] Run: 2
[0] Run: 2
[0] Run: 2
[0] Run: 2
continueWith 似乎只会等待第一个任务 (0) 而不管后续链。
我可以通过在第一个 Continuewith 块中嵌套第二个 ContinueWith 来解决问题。
我的代码有问题吗?我假设 Console.WriteLine 尊重 FIFO。
简而言之,您希望 ContinueWith
等待先前返回的对象。在 ContinueWith
操作中返回一个对象(即使是 Task
)对返回值没有任何作用,它不会等待它完成,它 returns 它并传递给继续(如果存在)。
确实发生了以下事情:
- 你运行
SampleAsyncMethodAsync(0, scopeCount.ToString())
完成后执行续1:
return SampleAsyncMethodAsync(1, scopeCount.ToString());
当它偶然发现 await Task.Run
时,它 returns 成为一项任务。即,它不会等待 SampleAsyncMethodAsync 完成。
- 那么continuation 1就认为完成了,因为它返回了一个值(task)
- 续集 2 是 运行。
如果您手动等待每个异步方法,那么它将 运行 因此:
for (int count = 0; count < 3; count++)
{
int scopeCount = count;
var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
.ContinueWith((prevTask) =>
{
SampleAsyncMethodAsync(1, scopeCount.ToString()).Wait();
})
.ContinueWith((prevTask2) =>
{
SampleAsyncMethodAsync(2, scopeCount.ToString()).Wait();
});
}
使用 ContinueWith(async t => await SampleAsyncMethodAsync...
也不起作用,因为它会导致包装 Task<Task>
结果(很好地解释 here)。
此外,您可以执行以下操作:
for (int count = 0; count < 3; count++)
{
int scopeCount = count;
var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
.ContinueWith((prevTask) =>
{
SampleAsyncMethodAsync(1, scopeCount.ToString())
.ContinueWith((prevTask2) =>
{
SampleAsyncMethodAsync(2, scopeCount.ToString());
});
});
}
但是,它创建了某种回调地狱并且看起来很乱。
您可以使用 await
使这段代码更简洁:
for (int count = 0; count < 3; count++)
{
int scopeCount = count;
var d = Task.Run(async () => {
await SampleAsyncMethodAsync(0, scopeCount.ToString());
await SampleAsyncMethodAsync(1, scopeCount.ToString());
await SampleAsyncMethodAsync(2, scopeCount.ToString());
});
}
现在,它 运行3 个任务有 3 个计数,因此每个任务将 运行 异步方法 number
等于 1、2 和 3。
我正在测试 C# 的异步性 async/await 并意外发现 ContinueWith 的后续代码不等待上一个任务完成:
public async Task<int> SampleAsyncMethodAsync(int number,string id)
{
Console.WriteLine($"Started work for {id}.{number}");
ConcurrentBag<int> abc = new ConcurrentBag<int>();
await Task.Run(() => { for (int count = 0; count < 30; count++) { Console.WriteLine($"[{id}] Run: {number}"); abc.Add(count); } });
Console.WriteLine($"Completed work for {id}.{number}");
return abc.Sum();
}
使用以下测试方法执行:
[Test]
public void TestAsyncWaitForPreviousTask()
{
for (int count = 0; count < 3; count++)
{
int scopeCount = count;
var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
.ContinueWith((prevTask) =>
{
return SampleAsyncMethodAsync(1, scopeCount.ToString());
})
.ContinueWith((prevTask2) =>
{
return SampleAsyncMethodAsync(2, scopeCount.ToString());
});
}
}
输出显示运行 0.0、1.0 和 2.0 的执行异步正确执行,但随后的 x.1 和 x.2 几乎立即开始并且 x.2 实际上在 x.1 之前完成。例如。如下所示:
[2] Run: 0
[2] Run: 0
[2] Run: 0
Completed work for 2.0
Started work for 0.1
Started work for 0.2 <-- surprise!
[0] Run: 2
[0] Run: 2
[0] Run: 2
[0] Run: 2
[0] Run: 2
continueWith 似乎只会等待第一个任务 (0) 而不管后续链。 我可以通过在第一个 Continuewith 块中嵌套第二个 ContinueWith 来解决问题。
我的代码有问题吗?我假设 Console.WriteLine 尊重 FIFO。
简而言之,您希望 ContinueWith
等待先前返回的对象。在 ContinueWith
操作中返回一个对象(即使是 Task
)对返回值没有任何作用,它不会等待它完成,它 returns 它并传递给继续(如果存在)。
确实发生了以下事情:
- 你运行
SampleAsyncMethodAsync(0, scopeCount.ToString())
完成后执行续1:
return SampleAsyncMethodAsync(1, scopeCount.ToString());
当它偶然发现
await Task.Run
时,它 returns 成为一项任务。即,它不会等待 SampleAsyncMethodAsync 完成。- 那么continuation 1就认为完成了,因为它返回了一个值(task)
- 续集 2 是 运行。
如果您手动等待每个异步方法,那么它将 运行 因此:
for (int count = 0; count < 3; count++)
{
int scopeCount = count;
var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
.ContinueWith((prevTask) =>
{
SampleAsyncMethodAsync(1, scopeCount.ToString()).Wait();
})
.ContinueWith((prevTask2) =>
{
SampleAsyncMethodAsync(2, scopeCount.ToString()).Wait();
});
}
使用 ContinueWith(async t => await SampleAsyncMethodAsync...
也不起作用,因为它会导致包装 Task<Task>
结果(很好地解释 here)。
此外,您可以执行以下操作:
for (int count = 0; count < 3; count++)
{
int scopeCount = count;
var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
.ContinueWith((prevTask) =>
{
SampleAsyncMethodAsync(1, scopeCount.ToString())
.ContinueWith((prevTask2) =>
{
SampleAsyncMethodAsync(2, scopeCount.ToString());
});
});
}
但是,它创建了某种回调地狱并且看起来很乱。
您可以使用 await
使这段代码更简洁:
for (int count = 0; count < 3; count++)
{
int scopeCount = count;
var d = Task.Run(async () => {
await SampleAsyncMethodAsync(0, scopeCount.ToString());
await SampleAsyncMethodAsync(1, scopeCount.ToString());
await SampleAsyncMethodAsync(2, scopeCount.ToString());
});
}
现在,它 运行3 个任务有 3 个计数,因此每个任务将 运行 异步方法 number
等于 1、2 和 3。