简体   繁体   中英

Async Await In Linq .TakeWhile

I am trying to convert my foreach function to a linq function

here is my normal code [Works fine]

    var tList = new List<Func<Task<bool>>> { Method1, Method2 };
    tList.Shuffle();

    int succeed = 0;
    foreach (var task in tList)
    {
        var result = await task();

        if(!result)
            break;

        succeed += 1;
    }

    MessageBox.Show(succeed == 1 ? "Loading complete." : "Something went wrong!");

And here is after converted to Linq [Giving 2 compiler errors]

    var tList = new List<Func<Task<bool>>> { Method1, Method2 };
    tList.Shuffle();

    int succeed = tList.Select(async task => await task()).TakeWhile(result => result).Count();

    MessageBox.Show(succeed == 1 ? "Loading complete." : "Something went wrong!");

Errors

  • Cannot convert lambda expression to delegate type 'System.Func,bool>' because some of the return types in the block are not implicitly convertible to the
    delegate return type
  • Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'bool'

I wonder why the compiler is giving those messages, so any help will be appreciated.

Note : i also tried .TakeWhile(async result => await result) with that error

  • The return type of an async method must be void, Task, or Task T

Method1 and Method2 if someone wanna them :

public async Task<bool> Method1()
{
    await Task.Delay(1000);
    Console.WriteLine("Method1");
    return false;
}

public async Task<bool> Method2()
{
    await Task.Delay(1000);
    Console.WriteLine("Method2");
    return true;
}

One possible solution

Use a TakeWhileAsync extension method like this:

public static async Task<IEnumerable<T>> TakeWhileAsync<T>(this IEnumerable<Task<T>> tasks, Func<T, bool> predicate)
{
    var results = new List<T>();

    foreach (var task in tasks)
    {
        var result = await task;
        if (!predicate(result))
            break;

        results.Add(result);
    }

    return results;
}

There must be a more optimized solution but this is easy to read in my opinion. You iterate though the tasks, wait for each one to finish then do the same logic the regular TakeWhile method does.

First you need to actually call your methods: tList.Select(func => func() then apply the extension method over this newly created IEnumerable<Task<bool>> : tList.Select(func => func()).TakeWhileAsync(result => result)

You then await this and count the result: (await tList.Select(func => func()).TakeWhileAsync(result => result)).Count()

Why your attempts did not work

.Select(async task => await task()).TakeWhile(result => result)

Remember that an async function always returns a Task (or void). The lambda inside your select block returns an IEnumerable<Task<bool>> . When you apply .TakeWhile on this, your TakeWhile predicate ( result => result ) also returns a Task<bool> , because every element of your sequence at that point is a Task<bool> ! That causes an error because it should return bool .

.TakeWhile(async result => await result)

Same issue, your lambda returns a Task (because it is an async "method"), that will never work as a LINQ predicate.

.TakeWhile(result => result.Result)

This one (from Simon's answer) does compile, and will even work in some situations. (Where the SynchronizationContext is not tied to a single thread, but that's another long topic.) The main takeaway however is that blocking on async code is dangerous and will potentially lead to deadlocks . Here is a great detailed article on this.

There's a fundamental difference between the first and the second code (which do not do the same thing).

On the first code you are iterating over a list of tasks. On the second you are just transforming each task into another - the return type of an async lambda will always be a task of something.

Because you area awaiting sequentially on each task they really running in sequence. Is that what you intended to do?

Try something like this (not tested):

var tList = new List<Func<Task<bool>>> { Method1, Method2 };
tList.Shuffle();

int succeed = Task.WaitAll(tList.ToArray())
    .TakeWhile(result => result).Count();

MessageBox.Show(
    succeed == 1
    ? "Loading complete."
    : "Something went wrong!");

But that's still a strange piece of code.

This is simply because you cannot convert Task<bool> to bool .. as the compiler has instructed.

Change this:

.TakeWhile(result => result)

..to this:

.TakeWhile(result => result.Result)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM