我需要对我的数据库执行大量数据插入。我可以使用限制调度程序以多线程方式实现代码,该调度程序限制并发操作的数量。在每个M
行上,形成一个块并作为原子操作插入到数据库中。应该发生多个并发操作,因为数据库比读取和解析数据文件慢。我经常使用多线程来实现这个模型。
如果我决定使用await / async实现我的代码(实体框架支持异步编程),如何确保不超过N个并发任务执行(即转到数据库)at同时?
在我的初始设计中,我实例化了一个List<Task>
,一读到要以原子方式插入的数据块就添加了新任务,然后让我的方法在await
之后返回任务。设计时问题是并发Task
的数量(以及内存占用量)将会爆炸,因为任务的输送速度比完成大数据文件要快。
我在考虑使用SemaphoreSlim
,但我对异步编程的经验不多(与多线程不同)。所以我问这个问题是否有关于最佳实践的反馈,如果有的话。
答案 0 :(得分:1)
设计时问题是并发任务的数量(以及因此内存占用量)将会爆炸,因为任务的执行速度比完成大数据文件要快。我在考虑使用SemaphoreSlim
是的,SemaphoreSlim
是限制并发异步操作的合适选择:
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(10);
async Task ThrottledWorkAsync()
{
await _semaphore.WaitAsync();
try
{
await WorkAsync();
}
finally
{
_semaphore.Release();
}
}
...然而
如果我决定使用await / async实现我的代码(实体框架支持异步编程),我如何确保不会同时执行N个并发任务(即转到数据库)?
需要注意的一点是,实体框架 - 虽然它支持异步API - 仍然需要每个请求一个连接。因此,您不能拥有多个具有相同DbContext
的并发异步请求;您需要为每个并发请求创建一个单独的连接(或至少N个并发请求“借用”的连接)。
答案 1 :(得分:1)
如果您最初要插入n
个值(n
是最大并发任务数),您可以采取以下方法:
InsertAsync()
n
次。InsertAsync()
的新电话(重复2)。这样,您不需要使用信号量来控制并发级别,并且不会阻塞。
I've just published a package对此方案有用,它会公开2个方法Times()
和Map()
:https://github.com/jorgebay/concurrent-utils
例如:
// Execute MyMethodAsync() 1,000,000 times limiting the maximum amount
// of parallel async operations to 512
await ConcurrentUtils.Times(1000000, 512, (index) => MyMethodAsync(index));
答案 2 :(得分:0)
我使用这段代码来执行我的线程:
public static async Task WhenAll(this List<Func<Task>> actions, int threadCount)
{
var executeTaskHelper = new ConcurrentTaskHelper(threadCount);
return executeTaskHelper.Execute(actions);
}
public class ConcurrentTaskHelper
{
int _threadCount;
CountdownEvent _countdownEvent;
SemaphoreSlim _throttler;
public ConcurrentTaskHelper(int threadCount)
{
_threadCount = threadCount;
_throttler = new SemaphoreSlim(threadCount);
}
public async Task Execute(List<Func<Task>> tasks)
{
_countdownEvent = new CountdownEvent(tasks.Count);
foreach (var task in tasks)
{
await _throttler.WaitAsync();
Execute(task);
}
_countdownEvent.Wait();
}
private async Task Execute(Func<Task> task)
{
try { await task(); }
finally { Completed(); }
}
private void Completed()
{
_throttler.Release();
_countdownEvent.Signal();
}
}
此代码基于此主题中提供的代码:How to limit the amount of concurrent async I/O operations?
不使用CountdownEvent,最好实现AsyncCountdownEvent。这样就可以使用_await countdownEvent.WaitAsync();
调用它应该看起来像这样。它将执行所有任务,但只有40个(在这种情况下)并发:
var tasks = new List<Func<Task>>();
tasks.Add(() => saveAsync());
//add more
await tasks.WhenAll(40);