我有一个简单的控制台应用程序,我想在循环中调用许多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.
}
答案 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();
}