【问题标题】:Converting an iteration to Async将迭代转换为异步
【发布时间】:2014-11-10 12:22:06
【问题描述】:

我正在返回大量 DataTable 行,对其进行迭代并将每行的值推送到 Web 服务,然后返回响应代码(字符串)。如果 webservice 出现任何错误,整个过程将停止并显示错误:

Protected Sub DoStuff(sender As Object, e As EventArgs) Handles lnk_orderCards.Click
    Dim dt as DataTable = GetDataBaseData()
    For each dr as DataRow in dt.Rows()
        Dim f as String = dr.Item("firstname").ToString()
        Dim m as String = dr.Item("middleName").ToString()
        Dim s as String = dr.Item("surname").ToString()
        Dim err as String = String.Empty
        Dim result as String = XYZService.DoIt(f, m, s)
        Select Case result
            Case "ok"
                ' OK - allow For Loop Next '
            Case "e"
                err = "Some error"
            Case "e2"
                err = "Another error"
        End Select
        If Not String.IsNullOrWhiteSpace(err) Then
            ShowError(err)
            Exit Sub
        End If
    Next
    XYZService.Complete()
    ltl_status.Text = "Success!"
End Sub

我认为上述方法非常适合异步方法,尤其是在数据表有 1000 行的情况下,因为每个 Web 服务请求都可以并行发送。但是,从 MSDN 中,我找不到足够的示例来说明如何最好地实现异步。

谁能推荐一个更彻底的方法?我在MSDN 上阅读过关于Task.WaitAllTask.Factory.StartNew 的内容,但这些示例并不简单。如果使用Task.WaitAll like this,如果一个(或多个)任务失败,你如何停止流程?

重要的是,在调用XYZService.Complete() 之前,所有任务都返回成功。

基于 Stephen 输入的最终代码

Protected Async Sub DoStuff(sender As Object, e As EventArgs) Handles lnk_orderCards.Click
    Dim cts As New CancellationTokenSource
    Dim dt As DataTable = GetDataBaseData()
    Dim rows As IEnumerable(Of Task(Of String)) = (From dr As DataRow In dt.Rows Select DoServiceCall(cts, dr))
    Dim results() As String = Await Task.WhenAll(rows)
    Dim errors As List(Of String) = (From s As String In results Where s <> String.Empty).ToList()
    If errors.Count > 0 Then
        ShowError(String.Join("<br/>", errors))
        Exit Sub
    Else
        Console.WriteLine("Success")
    End If
End Sub

Protected Async Function DoServiceCall(t As CancellationTokenSource, dr As DataRow) As Task(Of String)
    If t.IsCancellationRequested Then
        t.Token.ThrowIfCancellationRequested()
    End If
    Dim f As String = dr.Item("firstname").ToString()
    Dim m As String = dr.Item("middleName").ToString()
    Dim s As String = dr.Item("surname").ToString()
    Dim returnResult As XYZService.ServiceReturnResult = Await XYZService.DoItAsync(f, s, s)
    Select Case returnResult.return
        Case "ok"
            ' OK - allow For Loop Next '
        Case Else
            t.Cancel(False)
            Throw New Exception("Web service error: " & returnResult.return)
    End Select
    Return returnResult.return
End Function

【问题讨论】:

  • async/await 将不会并行执行任何操作。但是,它将允许 GUI 在等待时保持响应。如果要并行执行,请使用Task.Factory.StartNew
  • 然后我读到 ASP.Net 中的StartNew一个非常糟糕的主意 (bit.ly/1ueSZeG) 并且没有正确记录替代方案,整个事情只是变得更加混乱... :-/
  • 一劳永逸是个坏主意。如果您并行启动 100 个任务并等待它们,这不是“一劳永逸”。 (这仍然是一种可能的 DoS,因为一个请求会产生大量工作)
  • @DasKrümelmonster 这并不完全正确,而async 本身并不会导致方法神奇地异步执行,在点击第一个await 时,代码可能在不同的同步上下文中运行,因此,不同的线程。

标签: asp.net .net vb.net asynchronous asp.net-4.5


【解决方案1】:

最简单的解决方案是使用Task.WhenAll。具体来说,您可以将(LINQ 的 Select)每个项目投影到 Task (of T) 中,然后执行 Await Task.WhenAll

但是,这种简单的方法将同时执行所有请求,并且如果其中一个请求失败,则不会停止其他请求。这有点复杂。我建议使用CancellationTokenSource 来表示“紧急停止”,每个请求都会接收CancellationToken,如果失败则取消源。

不要使用Task.Run 或其更糟糕的表亲StartNew。由于您的操作本质上是异步的(I/O 绑定的网络请求),因此您需要异步并发 (Task.WhenAll),而不是并行并发 (StartNew)。

【讨论】:

  • 首先,您需要将循环代码分解为一个单独的方法。这将引导您找到更好的解决方案。您需要传入 CancellationTokenSource 并使用异常来处理错误而不是字符串。
  • 再次感谢。我根据您的评论进行了另一次修订。我在正确的轨道上吗?
  • 是的。我实际上会传入DataRow 并在DoServiceCall 方法中执行一切。然后,您可以很容易地将 foreach 循环更改为 LINQ Select 语句。
  • 看起来还不错(但我对VB的阅读不是很好)。我认为您只需要在 DoServiceCall 方法的开头添加一个取消令牌检查即可。
  • 我所说的“检查”实际上是指ThrowIfCancellationRequested
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-24
  • 2023-03-12
  • 2023-01-30
  • 2018-06-05
  • 2013-07-01
  • 1970-01-01
相关资源
最近更新 更多