Better way to Select to an async Func? - c#

I've been refactoring a common pattern in my project and found it's not as simple as using a LINQ Select to an async function.
For context, here is how it is done currently.
async Task<ICollection<Group>> ExecuteQueryGroupsForDomain(DomainInfo domain, int batchSize, CancellationToken ct)
{
try
{
return await BlahBlahActuallyGoGetGroupsForDomainHere(domain, batchSize, ct);
}
catch (Exception e)
{
return null;
}
}
var executeQueries = new List<Func<CancellationToken, Task<ICollection<Group>>>>();
domains.ForEach(domain =>
executeQueries.Add(async (ct) =>
await ExecuteQueryGroupsForDomain(domain, 123, ct)));
Now if I try to replace the ForEach loop section using LINQ:
var executeQueries = domains.Select(domain =>
async (ct) => await ExecuteQueryGroupsForDomain(domain, 123, ct));
It complains Type arguments cannot be inferred by the usage which leads me to believe I'm not returning anything from the Select, but I clearly am returning the Func I want.
Is there a better way of creating a list of Func's, ideally avoiding explicit casts? Also is there any explanation why the compiler is unable to infer the type when the Select'd async method is clearly telling it what the type should be?
To be clear, I do need to pass the CancellationToken to the Func's because it is a different token than the external one (specifically, it is a linked token tying the external one to another internal one).

The problem is in the returns of the select, for the compiler is not clear what the type of the return is. So, you need to explicitly the type of the return, here are 2 ways:
executeQueries = domains.Select(domain =>
new Func<CancellationToken, Task<ICollection<Group>>>(token =>
this.ExecuteQueryGroupsForDomain(domain, 123, token))).ToList();
executeQueries = domains
.Select<DomainInfo, Func<CancellationToken, Task<ICollection<Group>>>>(domain =>
ct => this.ExecuteQueryGroupsForDomain(domain, 123, ct)).ToList();
======================================================================
EDIT 1:
The compiler can't infer the type from a lambda expression because a lambda is just a shorthand for an anonymous method, not a type. So, you need to be explicitly and indicate the return type of the method, if the return is a base Delegate or other delegate type, like Action, Func, etc. Review this other answer, where explain the error compiler based on the C# 4 spec.
If you need transform your original code in something more readable, I don't think there is another way more readable. Here are other ways the code can be written:
foreach (var domain in domains) {
executeQueries.Add(token => this.ExecuteQueryGroupsForDomain(domain, 123, token));
}
executeQueries.AddRange(domains
.Select(domain => (Func<CancellationToken, Task<ICollection<Group>>>) (token =>
this.ExecuteQueryGroupsForDomain(domain, 123, token))));
executeQueries =
(from domain in domains
select new Func<CancellationToken, Task<ICollection<Group>>>(token =>
this.ExecuteQueryGroupsForDomain(domain, 123, token))).ToList()

Do you really need the Func's?
You can use the following if the actual CancellationToken is already present.
// create and start a Task for each domain
var executeQueryTasks = domains.Select(domain => ExecuteQueryGroupsForDomain(domain, 123, ct));
// wait until all tasks are finished and get the result in an array
var executedQueries = await Task.WhenAll(executeQueryTasks);

You may gain some readability by using an extension method like the one below. It takes the same arguments with the LINQ Select method, but returns task-factories instead of materialized tasks.
public static IEnumerable<Func<CancellationToken, Task<TResult>>> SelectTaskFactory
<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, CancellationToken, Task<TResult>> selector)
{
return source.Select(item =>
{
return new Func<CancellationToken, Task<TResult>>(ct => selector(item, ct));
});
}
Usage example:
var executeQueries = domains.SelectTaskFactory(async (domain, ct) =>
{
return await ExecuteQueryGroupsForDomain(domain, 123, ct);
}).ToList();
The type of the executeQueries variable is List<Func<CancellationToken, Task<ICollection<Group>>>>.

Related

Where clause in LINQ calling an async method

I have a method in a class defined as....
public static async Task<int> GetDCountAsync(int dId)
I'm trying to call this method within a LINQ Where clause....
var excessD= dToConsider
.Where(async x => await myService.GetDCountAsync(x.Id) >= x.Threshold)
.Select(x => x.Id)
.ToArray();
FYI: Threshold is an int.
I'm getting an error for async....
The return type of an 'async' anonymous function must be a 'void', 'Task', 'Task', ......
The return type of GetDCountAsync is async Task. Where am I going wrong? Thanks in advance!
You could use the functionality already available in the System.Linq.Async package:
int[] excessD = await dToConsider
.ToAsyncEnumerable()
.WhereAwait(async x => await myService.GetDCountAsync(x.Id) >= x.Threshold)
.Select(x => x.Id)
.ToArrayAsync();
The signature of the WhereAwait operator:
// Filters the elements of an async-enumerable sequence based on
// an asynchronous predicate.
public static IAsyncEnumerable<TSource> WhereAwait<TSource>(
this IAsyncEnumerable<TSource> source,
Func<TSource, ValueTask<bool>> predicate);
The asynchronous method myService.GetDCountAsync will be invoked and awaited for one element at a time, not for all of them concurrently.
So the full error message you are getting will be :
Cannot convert async lambda expression to delegate type 'Func<int, bool>'. An async lambda expression may return void, Task or Task, none of which are convertible to 'Func<int, bool>'.
What it's actually telling you is that a .Where() LINQ call is expecting to take an int, and return a boolean. But because you are trying to do an async call, it's instead trying to return Task.
Or in other words, the compiler is saying "I see you are using an async method here, I know that async methods can only return void, Task or Task, and I also know that this Where method expects a bool back, so I know right now this is not gonna work"
Part of the problem is that we don't actually know what type dToConsider is, and in some cases, it's going to look to do deferred execution. In any case, because you are doing custom code logic for the WHERE statement, let's assume that this is not EntityFramework or something that needs to be deferred and say it's just a list. In that case, simple using a typical ForEach loop to filter your list down.
var filteredItems = new List<T>();
foreach(var item in dToConsider)
{
if(await myService.GetDCountAsync(x.Id) >= x.Threshold)
filteredItems.Add(item);
}
I'm sure someone may answer with a nice extension method for you using Task.WhenAll etc, but the this is the simplest way to get what you need done.

Is there a way to combine LINQ and async

Basically I have a procedure like
var results = await Task.WhenAll(
from input in inputs
select Task.Run(async () => await InnerMethodAsync(input))
);
.
.
.
private static async Task<Output> InnerMethodAsync(Input input)
{
var x = await Foo(input);
var y = await Bar(x);
var z = await Baz(y);
return z;
}
and I'm wondering whether there's a fancy way to combine this into a single LINQ query that's like an "async stream" (best way I can describe it).
When you use LINQ, there are generally two parts to it: creation and iteration.
Creation:
var query = list.Select( a => a.Name);
These calls are always synchronous. But this code doesn't do much more than create an object that exposes an IEnumerable. The actual work isn't done till later, due to a pattern called deferred execution.
Iteration:
var results = query.ToList();
This code takes the enumerable and gets the value of each item, which typically will involve the invocation of your callback delegates (in this case, a => a.Name ). This is the part that is potentially expensive, and could benefit from asychronousness, e.g. if your callback is something like async a => await httpClient.GetByteArrayAsync(a).
So it's the iteration part that we're interested in, if we want to make it async.
The issue here is that ToList() (and most of the other methods that force iteration, like Any() or Last()) are not asynchronous methods, so your callback delegate will be invoked synchronously, and you’ll end up with a list of tasks instead of the data you want.
We can get around that with a piece of code like this:
public static class ExtensionMethods
{
static public async Task<List<T>> ToListAsync<T>(this IEnumerable<Task<T>> This)
{
var tasks = This.ToList(); //Force LINQ to iterate and create all the tasks. Tasks always start when created.
var results = new List<T>(); //Create a list to hold the results (not the tasks)
foreach (var item in tasks)
{
results.Add(await item); //Await the result for each task and add to results list
}
return results;
}
}
With this extension method, we can rewrite your code:
var results = await inputs.Select( async i => await InnerMethodAsync(i) ).ToListAsync();
^That should give you the async behavior you're looking for, and avoids creating thread pool tasks, as your example does.
Note: If you are using LINQ-to-entities, the expensive part (the data retrieval) isn't exposed to you. For LINQ-to-entities, you'd want to use the ToListAsync() that comes with the EF framework instead.
Try it out and see the timings in my demo on DotNetFiddle.
A rather obvious answer, but you have just used LINQ and async together - you're using LINQ's select to project, and start, a bunch of async Tasks, and then await on the results, which provides an asynchronous parallelism pattern.
Although you've likely just provided a sample, there are a couple of things to note in your code (I've switched to Lambda syntax, but the same principals apply)
Since there's basically zero CPU bound work on each Task before the first await (i.e. no work done before var x = await Foo(input);), there's no real reason to use Task.Run here.
And since there's no work to be done in the lambda after call to InnerMethodAsync, you don't need to wrap the InnerMethodAsync calls in an async lambda (but be wary of IDisposable)
i.e. You can just select the Task returned from InnerMethodAsync and await these with Task.WhenAll.
var tasks = inputs
.Select(input => InnerMethodAsync(input)) // or just .Select(InnerMethodAsync);
var results = await Task.WhenAll(tasks);
More complex patterns are possible with asynchronony and Linq, but rather than reinventing the wheel, you should have a look at Reactive Extensions, and the TPL Data Flow Library, which have many building blocks for complex flows.
Try using Microsoft's Reactive Framework. Then you can do this:
IObservable<Output[]> query =
from input in inputs.ToObservable()
from x in Observable.FromAsync(() => Foo(input))
from y in Observable.FromAsync(() => Bar(x))
from z in Observable.FromAsync(() => Baz(y))
select z;
Output[] results = await query.ToArray();
Simple.
Just NuGet "System.Reactive" and add using System.Reactive.Linq; to your code.

Async Await in Lambda expression where clause

I would want to call an async method inside lambda expression. Please help me doing the below
eg -
return xyz.Where(async x=> await AsyncMethodCall(x.val));
And the Async method looks like
public async Task<bool> AsyncMethodCall(Data d){...}
When I do the above, I get the following error
Error CS4010 Cannot convert async lambda expression to delegate type
'Func<Data, bool>'. An async lambda expression may return void, Task
or Task<T>, none of which are convertible to 'Func<Data, bool>'.
Thanks in advance for the help
Asynchronous sequences are tricky because you really have to think about what specifically you want the code to do.
For example, you could want to execute the AsyncMethodCall calls sequentially, and then return all the results:
var result = new List<T>();
foreach (var d in xyz)
if (await AsyncMethodCall(d.val))
result.Add(d);
return result;
Or, you could execute all the AsyncMethodCall calls concurrently, and then collect and return the results (again, all at once):
var tasks = xyz.Select(async d => new { d, filter = await AsyncMethodCall(d.val) });
var results = await Task.WhenAll(tasks);
return results.Where(x => x.filter).Select(x => x.d);
Or, you could execute all the AsyncMethodCall calls sequentially, and produce the results one at a time. This approach is incompatible with IEnumerable<T> (assuming you want to keep the call asynchronous). If you want to produce a sequence where AsyncMethodCall is asynchronously invoked during sequence enumeration, then you would need to change to IAsyncEnumerable<T>. If you want to produce a sequence that is started by the consumer and then produces results on its own, you would need to change to IObservable<T>.
Or, you could execute all the AsyncMethodCall calls concurrently, and produce the results one at a time. This is also incompatible with IEnumerable<T>; you would need to change to IObservable<T>. And you would also need to decide whether to maintain the original ordering, or to produce them in order of AsyncMethodCall completing.

Asynchronous LINQ

I was looking for an async .Where() but could not find one so after some research I've created one.
public static class LinqExtension
{
public static async Task<IEnumerable<T>> WhereAsync<T>(this IEnumerable<T> source, Func<T, Task<bool>> #delegate)
{
var tasks = source.Select(async t => new
{
Predicate = await #delegate(t).ConfigureAwait(false),
Value = t
}).ToList();
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
IEnumerable<T> typeList = results.Where(pred => pred.Predicate).Select(val => val.Value);
return typeList;
}
}
When I try to use it i get runtime error
Cannot convert implicit type bool to Task and yes it's correct
This is how I've tried
var q = await context.StockHistories.WhereAsync(x => x.ProductId == productId);
I've tried
context.StockHistories.WhereAsync(Task.Run(() => { x => x.ProductId == productId; }));
but getting
Only assignment, call, increment, decrement, and new object
expressions can be used as a statement
Can please someone provide a solution and explain what I am doing wrong?
The async methods for EF are the ones that execute the query. So what you actually want is
var q = await context.StockHistories.Where(x => x.ProductId == productId).ToListAsync();
Basically there isn't an asynchronous Where method because it doesn't make sense to have one because it's just used to generate the actual SQL that will be executed on the DB. The query isn't actually run until you iterate the results, and all the methods that do that have an asynchronous version.

C# Action<> with Func<> parameter

I have the following method that I can't figure out correct syntax to call:
public T GetAndProcessDependants<C>(Func<object> aquire,
Action<IEnumerable<C>, Func<C, object>> dependencyAction) {}
I'm trying to call it like this:
var obj = MyClass.GetAndProcessDependants<int>(() => DateTime.Now,
(() => someList, (id) => { return DoSomething(x); }) }
Edited:
thx everyone, you guys helped turned on a light bulb in my head. here is what i did:
var obj = MyClass.GetAndProcessDependants<int>(
() => DateTime.Now,
(list, f) =>
{
list = someList;
f = id => { return DoSomething(id); };
});
not sure why i even an issue with this. it's one of those days i guess..
thx
Your lambda syntax is totally wrong.
You need to create a single lambda expression with two parameters:
(list, id) => DoSomething(...)
Right now the function is only accepting a single argument, when it asks for two!
You need to accept a list argument, such as (list, id) => {}
Just looking at the description above, it looks like the call should be:
var obj = MyClass.GetAndProcessDependants<int>(() => DateTime.Now,
(seq, fun) => { /* do something with seq and fun */ });
The key is since you are passing an Action that takes a Func, the caller is (most likely) going to be the one passing that Func into your Action. So you just specify how that Func is applied to the sequence passed in (if I'm reading the prototype correctly).
var obj = MyClass.GetAndProcessDependants<int>(
() => DateTime.Now,
(someList, id) => DoSomething(x)
);

Categories