简体   繁体   English

在没有异步的情况下使用等待来完成任务

[英]Use await without async to process tasks as they complete

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 . 刚进入异步编程,我的最终目标是异步获取给定文件集的sha1哈希值,然后继续使用计算出的哈希值处理所述文件。 I found the following MSDN blog post speaks on how to process the task results as the tasks finish: Link 我发现以下MSDN博客文章谈到了如何在任务完成时处理任务结果: 链接

The entire article is written in C# and my project is in VB.NET. 整个文章都是用C#编写的,而我的项目是在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. 我试图自己将C#代码重写为VB,但是我必须错过关键的步骤,否则我无法完全理解Async / Await编程的过程/语法。

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. 错误1“等待”仅当包含在用“异步”修饰符标记的方法或lambda表达式中时才能使用。

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. 通过将Async添加到包含的Sub声明中,我可以使错误消失。 However, if I do that I receive another error because the containing method is main() and it's a console Application. 但是,如果这样做,我会收到另一个错误,因为包含的方法是main() ,它是一个控制台应用程序。

Error 1 The 'Main' method cannot be marked 'Async'. 错误1无法将“主要”方法标记为“异步”。

So I guess my question is, how can I use Await an asynchronous task without making the containing method Async? 所以我想我的问题是,如何在不使包含方法异步的情况下使用Await异步任务? My code below is just a tester for implementation in a WinForms project and I'd rather stay away from non native .NET pieces. 我下面的代码只是一个WinForms项目中实现的测试器,我宁愿远离非本机.NET片段。

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: 以下是我从C#转换的完整代码,以及用于计算文件sha1哈希值的少量代码:

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 as familiar with VB.NET as C#, but I think this may be answered here with async console programs . 我不像C#那样熟悉VB.NET,但是我认为这可以通过异步控制台程序来解决 Essentially, you want to wrap your async task in an AsyncContext, and run it with a task runner. 本质上,您希望将异步任务包装在AsyncContext中,并使用任务运行器运行它。 See here for another good example. 参见此处是另一个很好的例子。

EDIT: If you're looking to stay in native .NET, you can make your own task runner. 编辑:如果您希望保留在本机.NET中,则可以创建自己的任务运行器。 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;
}

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. await仅是等待Task结果的一种选择,其次是Task静态方法的用法,用于不时地等待所有任务的结果。
As far as I can tell, you are creating a list of Tasks of Tasks of T , something like this: 据我所知,你正在创建的列表TasksTasksT ,这样的事情:

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: 如果是这样,您可以做的最简单的事情就是将任务列表放平,然后用两次调用Task.WaitAll()方法等待其中的所有任务,如下所示:

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. 在这种情况下,您将获得TPL所有好处,因为有很多技术可以平衡工作,等等,并且您将获得所需的所有结果。

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. 如果Task.WaitAny()外观顺序获得结果,可以使用Task.WaitAny()方法编写一个循环,但是我认为这不是一个好选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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