2017-02-06 6 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()).

Проблема возникает, когда я хочу вызвать асинхронную функцию (IncAsync()). Результат, который я возвращаю, имеет тип 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 = ожидание GetResultsAsync (n => IncAsync (n) .Result, numbers);'? –

+2

Не используйте 'async void', это предназначено только для обработчиков событий. Вы не можете ждать метода «async void» –

+3

Также «ждут Task.WhenAll (задачи); return tasks.Select (t => t.Result); '? Зачем? Если все задачи имеют тип возврата, 'WhenAll' возвращает массив результатов. Очищая код, вы должны иметь возможность писать 'int [] results = await Task.WhenAll (tasks);' –

ответ

3

Необходимо создать две перегрузки GetResultsAsync. Нужно принять «синхронный» делегат, который возвращает 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); 
} 

Вторая перегрузка будет принимать «асинхронный» делегат, который возвращает Task<TResult>. Этот метод не требует, чтобы обернуть каждый делегат в задачу, потому что они уже задачи:

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); 
} 

Вы даже можете назвать второй метод от первого, чтобы избежать дублирования кода:

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); 
} 

ПРИМЕЧАНИЕ: Эти методы не упрощают вашу жизнь. Те же результаты могут быть достигнуты с

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. Для вашего второго случая нет смысла создавать Tasks около Tasks. Эквивалент будет:

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

, но мне нравится Сергей await Task.WhenAll(numbers.Select(IncAsync)) лучше.


Возможно, что мне очень нравится это стиль пар Linq перегрузок:

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; 
} 

Который, кстати, если вы измените Range(1,6) на Range(1,40) показывает преимущество асинхронного. На моей машине время синхронизации может резко повыситься, когда асинхронная версия остается на секунду или около того даже для Range(1, 100000)

+0

Это замечательно. Наконец, я пришел с сочетанием вашего ответа с Сергеем, используя '.AsParallel()', исходя из соображений производительности, упомянутых здесь [http://stackoverflow.com/a/19103047/7850]. К сожалению, я мог бы только дать одному из вас ответить на кредит ... :) –