组合任务会产生字典

时间:2016-03-01 14:09:03

标签: c# task

我正在尝试并行化依赖外部资源的工作,并将其合并为一个结果字典。

为了说明我的需要,想象一下我想下载一组文件,并将每个结果放在一个字典中,其中键是url:

string[] urls = { "http://msdn.microsoft.com", "http://www.stackoverflow.com", "http://www.google.com" };

var fileContentTask = GetUrls(urls);

fileContentTask.Wait();

Dictionary<string, string> result = fileContentTask.Result;
// Do something

然而,我能够编码GetUrls方法。我可以生成所有任务,但我没有找到如何在字典中合并结果:

static Task<Dictionary<string,string>> GetUrls(string[] urls)
{
    var subTasks = from url in urls
                   let wc = new WebClient()
                   select wc.DownloadStringTaskAsync(url);

    return Task.WhenAll(subTasks); // Does not compile
}

如何将生成的任务合并到字典中?

3 个答案:

答案 0 :(得分:11)

您需要自己执行映射。例如,您可以使用:

static async Task<Dictionary<string,string>> GetUrls(string[] urls)
{
    var tasks = urls.Select(async url =>
    {
        using (var client = new WebClient())
        {
            return new { url, content = await client.DownloadStringTaskAsync(url) };
        };
    }).ToList();
    var results = await Task.WhenAll(tasks);
    return results.ToDictionary(pair => pair.url, pair => pair.content);
}

请注意该方法必须async,以便您可以在其中使用await

答案 1 :(得分:1)

作为@ Jon的答案的替代方案,这里是另一个工作代码(请参阅注释以了解它为什么不起作用):

    private static Task<Dictionary<string, string>> GetUrls(string[] urls)
    {
        var tsc = new TaskCompletionSource<Dictionary<string, string>>();
        var subTasks = urls.ToDictionary(
            url => url,
            url =>
            {
                using (var wc = new WebClient())
                {
                    return wc.DownloadStringTaskAsync(url);
                }
            }
            );

        Task.WhenAll(subTasks.Values).ContinueWith(allTasks =>
        {
            var actualResult = subTasks.ToDictionary(
                task => task.Key,
                task => task.Value.Result
                );

            tsc.SetResult(actualResult);
        });

        return tsc.Task;
    }

答案 2 :(得分:0)

利用现有linq的东西:

static async Task<Dictionary<string, string>> GetUrls(string[] urls)
{
    IEnumerable<Task<string>> subTasks = from url in urls
                   let wc = new WebClient()
                   select wc.DownloadStringTaskAsync(url);

    var urlsAndData = subTasks.Zip(urls, async (data, url) => new { url, data = await data });

    return (await Task.WhenAll(urlsAndData)).ToDictionary(a => a.url, a => a.data);
}

但由于这不会处理WebClient,我会重构一个方法,使其如下所示。我还添加了一个Distinct来电,因为在制作字典时,下载两个仅相同的网址是毫无意义的。

static async Task<Dictionary<string, string>> GetUrls(string[] urls)
{
    var distinctUrls = urls
        .Distinct().ToList();

    var urlsAndData = 
        distinctUrls
        .Select(DownloadStringAsync)
        .Zip(distinctUrls, async (data, url) => new { url, data = await data });

    return (await Task.WhenAll(urlsAndData)).ToDictionary(a => a.url, a => a.data);
}

private static async Task<string> DownloadStringAsync(string url)
{
    using (var client = new WebClient())
    {
        return await client.DownloadStringTaskAsync(url);
    }
}