2017-02-06 32 views
2

は、ここで私が何をしたいの易しく書き直さダウンバージョンです: 非同期に非同期デリゲートを呼び出しますか?

private static int Inc(int input) 
{ 
    return input + 1; 
} 

private static async Task<int> IncAsync(int input) 
{ 
    await Task.Delay(200); 
    return input + 1; 
} 

private static async Task<IEnumerable<TResult>> GetResultsAsync<TInput, TResult>(Func<TInput, TResult> func, IEnumerable<TInput> values) 
{ 
    var tasks = values.Select(value => Task.Run(() => func(value))) 
         .ToList(); 
    await Task.WhenAll(tasks); 
    return tasks.Select(t => t.Result); 
} 

public async void TestAsyncStuff() 
{ 
    var numbers = new[] { 1, 2, 3, 4 }; 
    var resultSync = await GetResultsAsync(Inc, numbers); // returns IEnumerable<int> 
    Console.WriteLine(string.Join(",", resultSync.Select(n => $"{n}"))); 
    // The next line is the important one: 
    var resultAsync = await GetResultsAsync(IncAsync, numbers); // returns IEnumerable<Task<int>> 
} 

だから、基本的には、 GetResultsAsync()は、入力値のセットのための関数の結果を取得する一般的な方法であることを意図しています。 TestAsyncStuff()では、同期関数( Inc())を呼び出すためにどのように動作するかを見ることができます。

非同期関数()を呼びたいときに問題が発生します。私が戻った結果はタイプIEnumerable<Task<int>>です。私はその結果にTask.WhenAll()を行うことができ、それが動作します:

var tasksAsync = (await GetResultsAsync(IncAsync, numbers)).ToList(); 
await Task.WhenAll(tasksAsync); 
var resultAsync = tasksAsync.Select(t => t.Result); 
Console.WriteLine(string.Join(",", resultAsync.Select(n => $"{n}"))); 

しかし、私は、コードを引き締めるとawaitインラインをしたいと思います。

var resultAsync = await GetResultsAsync(async n => await IncAsync(n), numbers); 

でも、これはIEnumerable<Task<int>>を返します。私はこれを行うことができます:

var resultAsync = await GetResultsAsync(n => IncAsync(n).GetAwaiter().GetResult(), numbers); 

そして、それは動作します...しかし、私が見たものから、Task.GetAwaiter().GetResult()またはTask.Resultの使用が奨励されていません。

これを行う正しい方法は何ですか?

+0

'var resultAsync = await GetResultsAsync(n => IncAsync(n).Result、numbers);' –

+2

'async void'は使用しないでください。これはイベントハンドラ専用です。 'async void'メソッドを待つことはできません –

+3

また' await Task.WhenAll(tasks);タスクを返します。選択(t => t.Result); '?どうして?すべてのタスクに戻り値の型がある場合、 'WhenAll'は結果の配列を返します。コードをクリーンアップすると、 'int [] results = Await Task.WhenAll(tasks);' –

答えて

3

GetResultsAsyncという2つのオーバーロードを作成する必要があります。 TResultを返す「同期」デリゲートを受け入れる必要があります。この方法は、タスクに各デリゲートをラップして、非同期にそれらを実行します:

private static async Task<IEnumerable<TResult>> GetResultsAsync<TInput, TResult>(
    Func<TInput, TResult> func, IEnumerable<TInput> values) 
{ 
    var tasks = values.Select(value => Task.Run(() => func(value))); 
    return await Task.WhenAll(tasks); 
} 

2番目のオーバーロードはTask<TResult>「を返す非同期」デリゲートを、受け入れます。

private static async Task<IEnumerable<TResult>> GetResultsAsync<TInput, TResult>(
    Func<TInput, TResult> func, IEnumerable<TInput> values) 
{ 
    return await GetResultsAsync(x => Task.Run(() => func(x)), values); 
} 

private static async Task<IEnumerable<TResult>> GetResultsAsync<TInput, TResult>(
    Func<TInput, Task<TResult>> func, IEnumerable<TInput> values) 
{ 
    var tasks = values.Select(value => func(value)); 
    return await Task.WhenAll(tasks); 
} 

あなたも、コードの重複を避けるために、最初の1から第二の方法を呼び出すことができます。彼らはすでに作業しているので、この方法では、タスクに各デリゲートをラップする必要はありません。


注:これらの方法では、人生を大幅に単純化するものではありません。あなたはより良い読み込み何かをしたい:同じ結果は、私はあなたの懸念は文体一つであると言うだろう

var resultSync = await Task.WhenAll(numbers.Select(x => Task.Run(() => Inc(x)))); 
var resultAsync = await Task.WhenAll(numbers.Select(IncAsync)); 
0

で達成することができます。あなたの最初のケースについて考えてみます。

var resultSync= numbers.AsParallel()/*.AsOrdered()*/.Select(Inc); 

PLINQは、すでにあなたが何をしようとしてしていることを理由に:それはIEnumerablesを並列化します。 2番目のケースでは、Tasksの周りにTasksを作成する必要はありません。同等は次のようになります。

var resultAsync = numbers.AsParallel()./*AsOrdered().*/Select(n => IncAsync(n).Result); 

が、私はセルゲイのawait Task.WhenAll(numbers.Select(IncAsync))より良いが好き。あなたが変更した場合、ちなみに、Range(1,40)からRange(1,6)は、非同期の利点を示し

var numbers = Enumerable.Range(1,6); 
var resultSync = await Enumerable.Range(1,6).SelectAsync(Inc); 
var resultAsync = await Enumerable.Range(1,100).SelectAsync(IncAsync); 

Console.WriteLine("sync" + string.Join(",", resultSync)); 
Console.WriteLine("async" + string.Join(",", resultAsync)); 


static class IEnumerableTasks 
{ 
    public static Task<TResult[]> SelectAsync<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> func) 
    { 
     return Task.WhenAll(source.Select(async n => await Task.Run(()=> func(n)))); 
    } 

    public static Task<TResult[]> SelectAsync<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> func) 
    { 
     return Task.WhenAll(source.Select(func)); 
    } 
} 
static int Inc(int input) 
{ 
    Task.Delay(1000).Wait(); 
    return input+1; 
} 

static async Task<int> IncAsync(int input) 
{ 
    await Task.Delay(1000); 
    return input + 1; 
} 


はおそらく、私が本当に好きな過負荷のLINQのスタイルのペアです。私のマシンでは、同期のタイミングは、非同期バージョンが1秒かそれ以上にとどまっているところで急激に上昇することがあります。Range(1, 100000)

+0

これは素晴らしいです。私は最終的にあなたの答えをSergey'sと組み合わせ、 '.AsParallel()'を使って、ここで言及されたパフォーマンスの考慮事項に基づいて行った(http://stackoverflow.com/a/19103047/7850)。残念ながら、あなたのうちの1人だけがクレジットに回答することができます:) –

関連する問題