【问题标题】:Async in 4.5: Cancellation and Timeout4.5 中的异步:取消和超时
【发布时间】:2013-04-09 05:23:23
【问题描述】:

我在 HTTPHandler 中有一个长时间运行的进程,我使用 Async Await .net 4.5 异步执行该进程

这完全符合预期......

如何添加超时,以便在处理时间过长时返回字符串“timeout”?

Protected Async Sub button1_Click(sender As Object, e As System.EventArgs)
    Dim asyncHandler = New AsyncHandler
    Await asyncHandler.ProcessRequestAsync(HttpContext.Current)
    Response.Write(asyncHandler.Result)
End Sub

Public Class AsyncHandler
    Inherits HttpTaskAsyncHandler

    Public Property Result As String

    Public Async Function ProcessRequestAsync(context As HttpContext) As Task
        Me.Result = Await DoLongRunningProcessAsync(context)
    End Function

    Private Function DoLongRunningProcessAsync(context As HttpContext) As Task(Of String)

        'TODO: add a timeout so that if this takes too long we return "timeout":

        Return Task.Run(Of String)(Function() DoLongRunningProcess(context))

    End Function

    Private Function DoLongRunningProcess(context As HttpContext) As String
        'perform long running process....

        Return "success"
    End Function

End Class

【问题讨论】:

    标签: asp.net vb.net async-await asp.net-4.5


    【解决方案1】:

    我不使用 VB.NET,但我通常在 C# 中执行此操作,如下例所示:

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    
    // Make sure that we have a way to cancell long running 
    Task<SomeClass> longRunningTask = GetSomethingAsync(cancellationTokenSource.Token);
    
    // One of the task should be finished
    if (longRunningTask == await Task.WhenAny(longRunningTask, Task.Delay(30000)))
    {
         // Long running task completed
         SomeClass result = await longRunningTask;
    }
    else
    {
         // Task.Delay(30000) was finished
         cancellationTokenSource.Cancel();
    }
    

    【讨论】:

    • CancellationTokenSource 可以将时间跨度作为构造函数参数,这大大简化了这种方法。
    • +1 到 @StephenCleary - 还有典型的“毫秒作为 int”重载可用 - msdn.microsoft.com/en-us/library/…
    【解决方案2】:

    通常,支持超时的 TPL 代码通过 @outcoldman 和 @StephenCleary 提到的 CancellationToken(Source) 来实现。但是,在您的场景中,您说您希望超时路径也返回一个字符串,只是一个不同的字符串。

    因此,与其使用 CancellationToken(Source),不如将超时代码视为另一个生成字符串的任务,然后选择首先完成的任务(超时任务或“真实”任务),然后返回那个字符串。

    我将您的 DoLongRunningProcess(最里面的函数)更改为异步并返回 Task(Of String),这样您就不必执行 Task.Run 了。

    'Await Await' 可能看起来很奇怪,但是 Task.WhenAny 返回一个 Task(Of Task(Of T)) 所以第一个 Await 是到达首先完成的特定任务,然后我们等待它以便得到实际的字符串结果。

    Sub Main
        MainAsync().Wait()
    End Sub
    
    ' Define other methods and classes here
    Public Async Function MainAsync As Task
        Dim asyncHandler = New AsyncHandler
        Await asyncHandler.ProcessRequestAsync(System.Web.HttpContext.Current)
        Console.WriteLine(asyncHandler.Result)
    End Function
    
    Public Class AsyncHandler
        Inherits HttpTaskAsyncHandler
    
        Public Property Timeout As TimeSpan = TimeSpan.FromSeconds(10)
    
        Public Property Result As String
    
        Public Overrides Async Function ProcessRequestAsync(context As HttpContext) As Task
            Me.Result = Await DoLongRunningProcessAsync(context)
        End Function
    
        Private Async Function DoLongRunningProcessAsync(context As HttpContext) As Task(Of String)
    
            'TODO: add a timeout so that if this takes too long we return "timeout":
    
            Dim workerTask As Task(Of String) = DoLongRunningProcess(context)
            Dim cancelTask As Task(Of String) = DoTimeout()
    
            Return Await Await Task.WhenAny(workerTask, cancelTask)
    
        End Function
    
        Private Async Function DoTimeout() As Task(Of String)
            Await Task.Delay(Timeout)
    
            Return "timeout"
        End Function
    
        Private Async Function DoLongRunningProcess(context As HttpContext) As Task(Of String)
            'perform long running process....
    
            Await Task.Delay(TimeSpan.FromSeconds(15))
    
            Return "success"
        End Function
    
    End Class
    

    【讨论】:

    • 很好的替代方法 - 但确实应该使用取消令牌
    • 同意@geo1701。 CancellationToken 应该受到青睐,因为在某些情况下,上述方法实际上可能最终导致 Web 服务器崩溃。
    【解决方案3】:

    这是我最终实现的:

    创建了一个超时值为 x 毫秒的 CancellationToken

    添加了一个try catch来捕获错误:OperationCanceledException

    Public Async Function ProcessRequestAsync(context As HttpContext) As Task
            Dim cts As CancellationTokenSource = New CancellationTokenSource(30000) 'eg: 30 seconds
            Try
                Me.ResultCode = Await DoLongRunningProcessAsync(HttpContext.Current, cts.Token)
            Catch ex As OperationCanceledException
                Me.ResultCode = "timeout"
            End Try
    End Function
    

    将 CancellationToken 传递给 DoLongRunningProcessAsync 方法的签名

    通过调用 ThrowIfCancellationRequested 检测超时

    Private Function DoLongRunningProcessAsync(context As HttpContext, ct As CancellationToken) As Task(Of String)
            Return Await Task.Run(Of String)(
                Function()
                    Dim resultCode As String = DoLongRunningProcess(context)
    
                    'detect timeout:
                    ct.ThrowIfCancellationRequested()
    
                    Return resultCode
                End Function)
    End Function
    

    这是我发现的一篇非常有用的文章:

    Async in 4.5: Enabling Progress and Cancellation in Async APIs

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-03
      • 1970-01-01
      • 2015-02-28
      • 2013-02-22
      • 2014-11-01
      • 2013-07-31
      • 2012-08-16
      • 2011-08-19
      相关资源
      最近更新 更多