1

Just getting into asynchronous programming and my end goal is to get the sha1 hashes of given set of files asynchronously and then continue processing said files with the computed hash value . I found the following MSDN blog post speaks on how to process the task results as the tasks finish: Link

The entire article is written in C# and my project is in VB.NET. I have attempted to rewrite the C# code to VB myself, however I must be missing a critical step or I am not fully understanding the process/syntax of Async/Await programming.

I am receiving the following error on the following lines:

Error 1 'Await' can only be used when contained within a method or lambda expression marked with the 'Async' modifier.

Dim t As Task(Of Integer) = Await bucket
Dim result As Integer= Await t

Dim t As Task(Of String) = Await bucket
Dim result As String = Await t

I can make the error go away by adding Async to the containing Sub declaration. However, if I do that I receive another error because the containing method is main() and it's a console Application.

Error 1 The 'Main' method cannot be marked 'Async'.

So I guess my question is, how can I use Await an asynchronous task without making the containing method Async? My code below is just a tester for implementation in a WinForms project and I'd rather stay away from non native .NET pieces.

Below is the full code that I have converted from C# along with my little bit of code that does the computing of the sha1 hashes for the files:

Option Strict On
Option Explicit On

Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks


Module Module1

    Async Sub main()
        ' From the MSDN article
        Dim taskArr As Task(Of Integer)() = {Task(Of Integer).Delay(3000).ContinueWith(Function(x) 3I), _
                                           Task(Of Integer).Delay(1000).ContinueWith(Function(x) 1I), _
                                           Task(Of Integer).Delay(2000).ContinueWith(Function(x) 2I), _
                                           Task(Of Integer).Delay(5000).ContinueWith(Function(x) 5I), _
                                           Task(Of Integer).Delay(4000).ContinueWith(Function(x) 4I)}

        For Each bucket As Task(Of Task(Of Integer)) In Interleaved(taskArr)
            Dim t As Task(Of Integer) = Await bucket  ' Error Here
            Dim result As Integer = Await t  ' Error Here
            Console.WriteLine("{0}: {1}", DateTime.Now, result)
        Next

        'My bit of code for computing the file hashes
        Dim tasks As New List(Of Task(Of String))
        Array.ForEach(New DirectoryInfo("C:\StackOverflow").GetFiles("*", SearchOption.AllDirectories), Sub(x) tasks.Add(getHashAsync(x)))

        For Each bucket As Task(Of Task(Of String)) In Interleaved(tasks)
            Dim t As Task(Of String) = Await bucket  ' Error Here
            Dim result As String = Await t  ' Error Here
            Console.WriteLine(result)
        Next
    End Sub

    ' Original C# code that I converted to VB myself
    Public Function Interleaved(Of T)(tasks As IEnumerable(Of Task(Of T))) As Task(Of Task(Of T))()
        Dim inputTasks As List(Of Task(Of T)) = tasks.ToList()
        Dim buckets() As TaskCompletionSource(Of Task(Of T)) = New TaskCompletionSource(Of Task(Of T))(inputTasks.Count - 1I) {}
        Dim results() As Task(Of Task(Of T)) = New Task(Of Task(Of T))(buckets.Length - 1I) {}

        For i As Integer = 0I To buckets.Count - 1I Step 1I
            buckets(i) = New TaskCompletionSource(Of Task(Of T))()
            results(i) = buckets(i).Task
        Next

        Dim continuation As New Action(Of Task(Of T))(Function(completed As Task(Of T))
                                                          Dim bucket As TaskCompletionSource(Of Task(Of T)) = buckets(Interlocked.Increment(-1I))
                                                          Return bucket.TrySetResult(completed)
                                                      End Function)

        For Each inputTask As Task(Of T) In inputTasks
            inputTask.ContinueWith(continuation, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default)
        Next

        Return results
    End Function

    ' Get the sha1 hash of the file
    Private Async Function getHashAsync(fle As FileInfo) As Task(Of String)
        Using strm As New IO.FileStream(fle.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)
            Return Await New Task(Of String)(Function() As String
                                                 Dim sb As New Text.StringBuilder()
                                                 Using sha1 As New System.Security.Cryptography.SHA1CryptoServiceProvider()
                                                     Array.ForEach(sha1.ComputeHash(strm), Sub(x As Byte) sb.Append(x.ToString("x2")))
                                                 End Using
                                                 Return sb.Append(" | ").Append(fle.FullName).ToString
                                             End Function)
        End Using

    End Function 

End Module
  • I'm not sure that this specific case is a good candidate for await/async as this is all largely CPU bound work that needs to be threaded off. Using just Tasks and the available waiting capabilities and results would suffice. @VMAtm has provided a good answer for this below. When you get into creating a WinForms application you will be able to decorate your events or controllers with Async so that async/await can propagate down the execution of your application where relevant. – davidallyoung Sep 01 '15 at 21:47

2 Answers2

2

I'm not as familiar with VB.NET as C#, but I think this may be answered here with async console programs. Essentially, you want to wrap your async task in an AsyncContext, and run it with a task runner. See here for another good example.

EDIT: If you're looking to stay in native .NET, you can make your own task runner. Going off a basic example, I imagine it would look something like this:

static void Main(string[] args)
{
    Task t = AsyncMain(args);
    t.Wait();
}

async static Task AsyncMain(string[] args)
{
    await Task.Delay(1000); // Real async code here.
    Console.WriteLine("Awaited a delay of 1s");
    return;
}
Community
  • 1
  • 1
Ivan Peng
  • 579
  • 5
  • 16
  • Appears as though `AsyncContext` is not native to the .NET framework as it is a member of the Nito AsyncEx NuGet package. My above code is just a tester for implementation in a WinForms project and I'd rather stay away from non native .NET pieces. – вʀaᴎᴅᴏƞ вєнᴎєƞ Aug 19 '15 at 03:04
2

await is only one option to wait the Task's result, second is a Task static methods usage for sinchroniously waiting for the results of all the tasks.
As far as I can tell, you are creating a list of Tasks of Tasks of T, something like this:

var list = new List<Task<Task<string>>>();

If so, the simplest thing you can do is flatten the list of tasks, and after that wait for all the tasks in it with Task.WaitAll() method called twice, like this:

var list = new List<Task<Task<string>>>();

Task.WaitAll(list.ToArray());
// now we aggregate the results
var gatheredTasks = list.Select(t => t.Result);
Task.WaitAll(gatheredTasks.ToArray());
foreach (var task in gatheredTasks)
{
    Console.WriteLine(task.Result);
}

In this case you'll gain all the bonuses from TPL, as there are a lot of techniques used to balance the work and so on, and you'll get all the results you need.

In case if you want to get results in order of their appearance, you can write a loop with Task.WaitAny() method, but this is not a good choice, in my opinion.

VMAtm
  • 27,943
  • 17
  • 79
  • 125