'What's the VB.NET equivalent of async delegate in C#?

I'm trying to convert the following extension method (source) from C# to VB:

public static Task ForEachAsync<T>(this IEnumerable<T> source,
                                   int dop, Func<T, Task> body)
{
    return Task.WhenAll(
        from partition in Partitioner.Create(source).GetPartitions(dop)
        select Task.Run(async delegate {
            using (partition)
                while (partition.MoveNext())
                    await body(partition.Current);
        }));
}

The regular equivalent of delegate is Sub(), AFAIK, but I didn't expect it to work in this situation because of the Async keyword (and it didn't). So, I tried using Function() instead:

<System.Runtime.CompilerServices.Extension>
Public Function ForEachAsync(Of T)(source As IEnumerable(Of T),
                                   dop As Integer, body As Func(Of T, Task)) As Task
    Return Task.WhenAll(
        From partition In Partitioner.Create(source).GetPartitions(dop)
        Select Task.Run(Async Function() 'As Task '<-- see below.
                            Using partition
                                Do While partition.MoveNext()
                                    Await body(partition.Current)
                                Loop
                            End Using
                        End Function))
End Function

But this still doesn't compile and shows the following errors:

  • At WhenAll:

    Overload resolution failed because no accessible 'WhenAll' can be called with these arguments:
        'Public Shared Overloads Function WhenAll(Of TResult)(tasks As IEnumerable(Of Task(Of TResult))) As Task(Of TResult())': Type parameter 'TResult' cannot be inferred.
        'Public Shared Overloads Function WhenAll(Of TResult)(ParamArray tasks As Task(Of TResult)()) As Task(Of TResult())': Type parameter 'TResult' cannot be inferred.
    
  • At Await body(partition.Current):

    'Await' may only be used in a query expression within the first collection expression of the initial 'From' clause or within the collection expression of a 'Join' clause.

  • [Warning] at Async Function(): (it goes away if I add As Task)

    Function '<anonymous method>' doesn't return a value on all code paths. A null reference exception could occur at run time when the result is used.

What am I doing wrong? And what is the correct way to do this in VB?



Solution 1:[1]

In C#, the async Lambda can be expressed with a delegate type or using the invocation operator () followed by the => token as a lambda operator to invoke an anonymous method:

Task.Run(async ()=> { } );
Task.Run(async delegate { } );

In VB.Net, an anonymous method can be invoked with a Lambda expression using Sub() or Function(), both in-lined and with Sub / End Sub, Function() / End Function blocks:

Task.Run(Async Sub() [operation on captured variables])
Task.Run(Sub()
             [operation on captured variables]
         End Sub))

Task.Run(Async Function() [operation on captured variables])
Task.Run(Function()
             Return [operation on captured variables]
         End Function))

VB.Net's LINQ to SQL doesn't allow to await in a Select clause, because:

Await may only be used in a query expression within the first collection expression of the initial From clause or within the collection expression of a Join clause

It's referenced in Stephen Toub's Async/Await FAQ.

Select Task.Run(Async Function() ... ) tries to return an IEnumerable(Of TResult) instead of an IEnumerable(Of Task).

More in Language-Integrated Query (LINQ) (Visual Basic).

Conversely, LINQ to Objects - working with IEnumerable/IEnumerable<T> collections without other intermediate providers - does allow the async/await patter on a Select method:

<Extension>
Public Function ForEachAsync(Of T)(source As IEnumerable(Of T), dop As Integer, body As Func(Of T, Task)) As Task
    Return Task.WhenAll(
        Partitioner.Create(source).GetPartitions(dop).
        Select(Function(p) (
                   Task.Run(Async Function()
                                Using p
                                    While p.MoveNext()
                                        Await body(p.Current)
                                    End While
                                End Using
                            End Function))))
End Function

The C# version of LINQ to SQL allows it instead.
Why, since the same rule should also apply to the C# implementation?

The .NET Language Strategy:

C#:

We will keep growing C# to meet the evolving needs of developers and remain a state of the art programming language. We will innovate aggressively, while being very careful to stay within the spirit of the language.

VB.Net:

We will keep a focus on the cross-language tooling experience, recognizing that many VB developers also use C#. We will focus innovation on the core scenarios and domains where VB is popular.

Thus, the VB and C# Coevolution asserted in 2010 has shifted: C# and VB.Net features update has been decoupled. Hence, given the new language strategy, VB.Net and C# don't show roughly equal adoption anymore.

Solution 2:[2]

Not really the answer to the question but, if it's truly asynchronous code, there's no need for partitioning the execution:

<System.Runtime.CompilerServices.Extension>
Public Function ForEachAsync(Of T)(
    source As IEnumerable(Of T),
    body As Func(Of T, Task)) As Task
    Return Task.WhenAll(        
        From item In source
        Select body(item))
End Function

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Visual Vincent