0

ループ内の多くのURLを呼び出し、その結果をデータベーステーブルに配置するシンプルなコンソールアプリがあります。.Net 4.5を使用しており、非同期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.
    }
4

3 に答える 3

1

あなたの解決策は受け入れられます。ただし、 TPL Dataflowを確認する必要があります。これにより、データフローの「メッシュ」(または「パイプライン」)を設定し、データフローを介してデータをプッシュできます。

この単純な問題の場合、DataflowはContinueWith(手動の継続は常に厄介だと思います)を取り除く以外に実際には多くを追加しません。ただし、将来、ステップを追加したり、データフローを変更したりする予定がある場合は、データフローを検討する必要があります。

于 2013-03-06T18:08:39.217 に答える
1

あなたの解決策はほとんど正しいですが、2つの小さな間違いがあります(どちらもコンパイラエラーを引き起こします)。ContinueWithまず、の結果を呼び出さないでくださいList.Add。タスクでcontinueを呼び出してから、リストに継続を追加する必要があります。これは、括弧を移動するだけで解決されます。Resultまた、を呼び出す必要がありますreponse Task

これは、2つの小さな変更を加えたセクションです。

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

もう1つのオプションは、一連のタスクを取得し、タスクが完了した順序でそれらを順序付ける方法を活用することです。これがそのようなメソッドの私の実装です:

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);
    }
}
于 2013-03-06T18:19:57.870 に答える
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();

}
于 2013-03-06T18:51:51.893 に答える