创建一个将多个I / O任务分组的任务

时间:2012-04-20 12:13:20

标签: .net multithreading asynchronous task-parallel-library sqlcommand

首先,我不确定我是否在问一些愚蠢的事情,所以在开始之前道歉。

我有一个方法,使用SqlCommand异步保存数据库中的实体集合,并返回一个代表异步操作的Task,到目前为止它运行良好。基本上它为每个实体生成一个SQL命令和一堆参数,并将它们全部添加到SqlCommand实例(参数编号)

但是,如果我尝试插入大量实体,当参数计数达到2100时,SQL操作将失败,因为这是限制。然后我想分批拆分实体集合,然后按顺序执行它们,然后返回一个在完成所有子任务之前不会完成的任务。 (可能是最后一个)

每个子任务都会得到一个Int32,表示已经更改了多少行,并且最终的任务必须返回所有这些行的总和。所以任务都是Task。如果一个失败,所有这些都必须“回滚”,因此它们必须共享连接和事务对象。

另外,我想确保我正确使用Task和SqlCommand的I / O完成端口,并且在SQL Server上的操作完成时没有线程等待/ spining,因为这个“Save” “从ASP.NET MVC中的异步控制器调用操作。

这里的正确方法是什么?

问候。

3 个答案:

答案 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。然后,你将完全无阻塞。