首先,我不确定我是否在问一些愚蠢的事情,所以在开始之前道歉。
我有一个方法,使用SqlCommand异步保存数据库中的实体集合,并返回一个代表异步操作的Task,到目前为止它运行良好。基本上它为每个实体生成一个SQL命令和一堆参数,并将它们全部添加到SqlCommand实例(参数编号)
但是,如果我尝试插入大量实体,当参数计数达到2100时,SQL操作将失败,因为这是限制。然后我想分批拆分实体集合,然后按顺序执行它们,然后返回一个在完成所有子任务之前不会完成的任务。 (可能是最后一个)
每个子任务都会得到一个Int32,表示已经更改了多少行,并且最终的任务必须返回所有这些行的总和。所以任务都是Task。如果一个失败,所有这些都必须“回滚”,因此它们必须共享连接和事务对象。
另外,我想确保我正确使用Task和SqlCommand的I / O完成端口,并且在SQL Server上的操作完成时没有线程等待/ spining,因为这个“Save” “从ASP.NET MVC中的异步控制器调用操作。
这里的正确方法是什么?
问候。
答案 0 :(得分:1)
如果您要拆分插件,但按顺序运行,则不需要为每次调用使用“任务”。您只需要正常的顺序代码。
创建一个启动Task并返回它的方法。在任务体中,只需执行您想要的任何正常顺序代码。完成后,退出任务正文,单个任务将完成。
答案 1 :(得分:1)
使用C#5(并假设基于任务的异步),这将非常简单:
async Task<IReadOnlyList<SomeType>> PerformUpdatesAsync(
IEnumerable<AnotherType> data)
{
var result = new List<SomeType>();
foreach (var batch in data.SplitIntoBatches(BatchSize))
result.Add(await PerformUpdateAsync(batch));
return result;
}
在C#5之前模拟异步foreach
比较困难,但你可以这样做,例如使用批处理队列来处理和“递归”lambda:
Task<SomeType[]> PerformUpdatesAsync(IEnumerable<AnotherType> data)
{
var batches = new Queue<IEnumerable<AnotherType>>(
data.SplitIntoBatches(BatchSize));
var result = new List<SomeType>();
var tcs = new TaskCompletionSource<SomeType[]>();
AsyncCallback onEndUpdate = null;
onEndUpdate =
ar =>
{
result.Add(EndUpdate(ar));
if (batches.Count == 0)
tcs.SetResult(result.ToArray());
else
BeginUpdate(batches.Dequeue(), onEndUpdate, null);
};
BeginUpdate(batches.Dequeue(), onEndUpdate, null);
return tcs.Task;
}
如果您不喜欢以这种方式使用闭包,您可以通过创建一个单独的类来完成相同的操作,该类将在其字段中保存所有局部变量。
框架中没有SplitIntoBatches()
,因此您必须自己编写(除非您已经这样做过)。
答案 2 :(得分:0)
这是一个很好的问题。这是一些代码:
Task<int>[] batchTasks = ...;//start the batches here. each task should return the count of changed rows
Task<int> sumTask = Task.Factory.ContinueWhenAll(batchTasks, _ => {
return batchTasks.Sum(x => x.Result);
});
这将为您提供单个批次的无阻塞总额。
启动批处理时,请务必使用BeginExecuteReader等异步SQL API。然后,你将完全无阻塞。