使用多个任务从大型集合中检索所有记录

时间:2016-05-11 15:08:13

标签: c# multithreading concurrency task

我正在开发一个调用外部服务的应用程序,并且必须将外部集合的所有条目添加到本地集合中。目前的问题是外部集合可能超过1000条记录,但返回的搜索结果最多只能包含20个项目。

为了速度,我认为使用一系列任务将是前进的方向,所以我提出了以下代码:

int totalCount = returnedCol.total_count;
        while (totalCount > myDict.Count)
        {
            int numberOfTasks = // logic to calculate how many tasks to run

            List<Task> taskList = new List<Task>();

            for (int i = 1; i <= numberOfTasks; i++)
            {
                Interlocked.Add(ref pageNumber, pageSize);

                Task<SearchResponse> testTask = Task.Run(() =>
                {
                    return ExternalCall.GetData(pageNumber, pageSize);
                });

                Thread.Sleep(100);

                taskList.Add(testTask);
                testTask.ContinueWith(o =>
                {
                    foreach (ExternalDataRecord dataiwant in testTask.Result.dataiwant)
                    {
                        if (!myDict.ContainsKey(dataiwant.id))
                            myDict.GetOrAdd(dataiwant.id, dataiwant);
                    }
                });
            }
            Task.WaitAll(taskList.ToArray());
        }

但是,这不会产生所有结果。 pageNumber变量每次都正确递增,但似乎并非所有任务结果都被分析(因为较小数据集上的单个线程上的相同逻辑会返回所有预期结果)。此外,我尝试在链(而不是循环)中声明单个任务,并且全部返回测试数据。似乎我传入Thread.Sleep()的值越高,结果被添加到本地集合中的越多(但这并不理想,因为这意味着该过程需要更长时间!)

目前,在600条记录的样本中,我只在myDict集合中添加了约150-200条记录。我错过了一些明显的东西吗?

2 个答案:

答案 0 :(得分:2)

我认为如果你对代码采用更实用,更少命令的方法,那么你遇到难以理解的问题的可能性就会大大降低。我认为这样的事情会产生同样的效果:

int totalCount = returnedCol.total_count;
var tasks = Enumerable.Range(1, totalCount / pageSize)
    .Select(async page => {
        await Task.Delay(page * 100);
        return ExternalCall.GetData(page, pageSize));
    })
    .ToArray();
myDict = (await Task.WhenAll(tasks))
    .ToDictionary(dataiwant => dataiwant.id);

上面的代码假设您仍然希望在限制请求之间等待100毫秒。如果您只是在那里Thread.Sleep()尝试解决您遇到的问题,可以进一步简化它:

int totalCount = returnedCol.total_count;
var tasks = Enumerable.Range(1, totalCount / pageSize)
    .Select(async page => await Task.Run(() => ExternalCall.GetData(page, pageSize)))
    .ToArray();
myDict = (await Task.WhenAll(tasks))
    .ToDictionary(dataiwant => dataiwant.id);

答案 1 :(得分:0)

您错过了ContinueWith()导致其他任务的事实,并且您没有添加您的taskList

更好的方法是使用自.NET 4.5以来可用的async / await。它为解决方案提供了一种不那么重的方法。

您可以将算法更改为:

public async Task Process()
{
    int totalCount = returnedCol.total_count;

    while (totalCount > myDict.Count)
    {
        int numberOfTasks = // logic to calculate how many tasks to run

        List<Task> taskList = new List<Task>();

        for (int i = 1; i <= numberOfTasks; i++)
        {
            Interlocked.Add(ref pageNumber, pageSize);

            taskList.Add(ProcessPage(pageNumber, pageSize));
        }

        await Task.WhenAll(taskList.ToArray());
    }
 }

 private async Task ProcessPage(int pageNumber, int pageSize)
 {
       SearchResponse result = await Task.Run(() => 
           ExternalCall.GetData(pageNumber, pageSize)).ConfigureAwait(false);

       foreach (ExternalDataRecord dataiwant in result.dataiwant)
       {
           myDict.GetOrAdd(dataiwant.id, dataiwant);
       }
 }

async关键字告诉编译器稍后会有awaitawait基本上处理了ContinueWith电话的详细信息。如果您真的希望ExternalCall在其他任务中发生,那么您只需await来自该调用的结果。