Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add an async "await" to an addrange select statement?

I have a function like this:

public async Task<SomeViewModel> SampleFunction()
{
    var data = service.GetData();
    var myList = new List<SomeViewModel>();

    myList.AddRange(data.select(x => new SomeViewModel
    {
        Id = x.Id,
        DateCreated = x.DateCreated,
        Data = await service.GetSomeDataById(x.Id)
    }

    return myList;
}

My await isn't working as it can only be used in a method or lambda marked with the async modifier. Where do I place the async with this function?

like image 637
Ben Avatar asked Sep 11 '14 14:09

Ben


People also ask

Does async await use thread pool?

The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active.

What does await do in C sharp?

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any.


3 Answers

You can only use await inside an async method/delegate. In this case you must mark that lambda expression as async.

But wait, there's more...

Select is from the pre-async era and so it doesn't handle async lambdas (in your case it would return IEnumerable<Task<SomeViewModel>> instead of IEnumerable<SomeViewModel> which is what you actually need).

You can however add that functionality yourself (preferably as an extension method), but you need to consider whether you wish to await each item before moving on to the next (sequentialy) or await all items together at the end (concurrently).

Sequential async

static async Task<TResult[]> SelectAsync<TItem, TResult>(this IEnumerable<TItem> enumerable, Func<TItem, Task<TResult>> selector)
{
    var results = new List<TResult>();
    foreach (var item in enumerable)
    {
        results.Add(await selector(item));
    }
    return results.ToArray();
}

Concurrent async

static Task<TResult[]> SelectAsync<TItem, TResult>(this IEnumerable<TItem> enumerable, Func<TItem, Task<TResult>> selector)
{
    return Task.WhenAll(enumerable.Select(selector));
}

Usage

public Task<SomeViewModel[]> SampleFunction()
{
    return service.GetData().SelectAsync(async x => new SomeViewModel
    {
        Id = x.Id,
        DateCreated = x.DateCreated,
        Data = await service.GetSomeDataById(x.Id)
    }
}
like image 149
i3arnon Avatar answered Dec 03 '22 07:12

i3arnon


You're using await inside of a lambda, and that lambda is going to be transformed into its own separate named method by the compiler. To use await it must itself be async, and not just be defined in an async method. When you make the lambda async you now have a sequence of tasks that you want to translate into a sequence of their results, asynchronously. Task.WhenAll does exactly this, so we can pass our new query to WhenAll to get a task representing our results, which is exactly what this method wants to return:

public Task<SomeViewModel[]> SampleFunction()
{
    return Task.WhenAll(service.GetData().Select(
        async x => new SomeViewModel
    {
        Id = x.Id,
        DateCreated = x.DateCreated,
        Data = await service.GetSomeDataById(x.Id)
    }));
}
like image 38
Servy Avatar answered Dec 03 '22 07:12

Servy


Though maybe too heavyweight for your use case, using TPL Dataflow will give you finer control over your async processing.

public async Task<List<SomeViewModel>> SampleFunction()
{
    var data = service.GetData();

    var transformBlock = new TransformBlock<X, SomeViewModel>(
        async x => new SomeViewModel
        {
            Id = x.Id,
            DateCreated = x.DateCreated,
            Data = await service.GetSomeDataById(x.Id)
        },
        new ExecutionDataflowBlockOptions
        {
            // Let 8 "service.GetSomeDataById" calls run at once.
            MaxDegreeOfParallelism = 8
        });

    var result = new List<SomeViewModel>();

    var actionBlock = new ActionBlock<SomeViewModel>(
        vm => result.Add(vm));

    transformBlock.LinkTo(actionBlock,
        new DataflowLinkOptions { PropagateCompletion = true });

    foreach (var x in data)
    {
        transformBlock.Post(x);
    }
    transformBlock.Complete();

    await actionBlock.Completion;

    return result;
}

This could be substantially less long-winded if service.GetData() returned an IObservable<X> and this method returned an IObservable<SomeViewModel>.

like image 24
Timothy Shields Avatar answered Dec 03 '22 06:12

Timothy Shields