async i / o并在结果可用时处理结果

时间:2013-03-06 17:56:58

标签: c# task-parallel-library .net-4.5 async-await

我有一个简单的控制台应用程序,我想在循环中调用许多Urls并将结果放在数据库表中。我正在使用.Net 4.5并使用async i / o来获取URL数据。这是我正在做的简化版本。除数据库操作外,所有方法都是异步的。你们看到有什么问题吗?有更好的优化方法吗?

   private async Task Run(){
        var items = repo.GetItems(); // sync method to get list from database
        var tasks = new List<Task>();

        // add each call to task list and process result as it becomes available 
        // rather than waiting for all downloads
        foreach(Item item in items){
            tasks.Add(GetFromWeb(item.url).ContinueWith(response => { AddToDatabase(response.Result);}));
        }
        await Task.WhenAll(tasks); // wait for all tasks to complete.
    }

    private async Task<string> GetFromWeb(url) {
       HttpResponseMessage response = await GetAsync(url);
       return await response.Content.ReadAsStringAsync();
    }

    private void AddToDatabase(string item){
        // add data to database.
    }

3 个答案:

答案 0 :(得分:1)

您的解决方案是可以接受的。但你应该看看TPL Dataflow,它允许你设置数据流“网格”(或“管道”),然后通过它推送数据。

对于一个简单的问题,Dataflow除了摆脱ContinueWith之外不会真正添加更多(我总是发现手动延续尴尬)。但是,如果您计划在将来添加更多步骤或更改数据流,则应该考虑使用Dataflow。

答案 1 :(得分:1)

你的解决方案非常正确,只有两个小错误(两者都会导致编译器错误)。首先,你不要在ContinueWith的结果上调用List.Add,你需要在任务上继续调用,然后将延续添加到你的列表中,只需移动一个括号就可以解决这个问题。您还需要在Result reponse上致电Task

以下是有两个小改动的部分:

tasks.Add(GetFromWeb(item.url)
    .ContinueWith(response => { AddToDatabase(response.Result);}));

另一种选择是利用一种方法,该方法接受一系列任务并按照它们完成的顺序对它们进行排序。这是我对这种方法的实现:

public static IEnumerable<Task<T>> Order<T>(this IEnumerable<Task<T>> tasks)
{
    var taskList = tasks.ToList();

    var taskSources = new BlockingCollection<TaskCompletionSource<T>>();

    var taskSourceList = new List<TaskCompletionSource<T>>(taskList.Count);
    foreach (var task in taskList)
    {
        var newSource = new TaskCompletionSource<T>();
        taskSources.Add(newSource);
        taskSourceList.Add(newSource);

        task.ContinueWith(t =>
        {
            var source = taskSources.Take();

            if (t.IsCanceled)
                source.TrySetCanceled();
            else if (t.IsFaulted)
                source.TrySetException(t.Exception.InnerExceptions);
            else if (t.IsCompleted)
                source.TrySetResult(t.Result);
        }, CancellationToken.None, TaskContinuationOptions.PreferFairness, TaskScheduler.Default);
    }

    return taskSourceList.Select(tcs => tcs.Task);
}

使用此代码可以变为:

private async Task Run()
{
    IEnumerable<Item> items = repo.GetItems(); // sync method to get list from database

    foreach (var task in items.Select(item => GetFromWeb(item.url))
        .Order())
    {
        await task.ConfigureAwait(false);
        AddToDatabase(task.Result);
    }
}

答案 2 :(得分:0)

虽然我也会使用Rx解决方案投入我的帽子

using System.Reactive;
using System.Reactive.Linq;
private Task Run()
{
    var fromWebObservable = from item in repo.GetItems.ToObservable(Scheduler.Default)
                            select GetFromWeb(item.url);

    fromWebObservable
                    .Select(async x => await x)
        .Do(AddToDatabase)
        .ToTask();

}