为什么使用Task.Start取消6个WebRequest需要比Thread.Start更长的时间

时间:2011-07-24 04:32:43

标签: .net vb.net

我正在测试自定义异步WebRequest类,以了解请求被取消的速度有多快,并发现一些奇怪的结果,用TPL启动6个WebRequests并立即取消它需要25秒,但是当我使用时只需要背景线程只花了5秒。

更新:在没有取消的情况下运行它们需要使用Task.Start 9秒和Thread.Start 3秒。

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

Module Module2

Dim closeEvent As New ManualResetEvent(False)
Dim sw As System.Diagnostics.Stopwatch

' first domain is invalid
Dim urls() As String = {
    "http://www.jbihgcgfxfdxdwewloevedfhvcdfb.com", 
    "http://www.hanselman.com/blog/",
    "http://www.stackoverflow.com/questions/",
    "http://www.finderscope.net",
    "http://msdn.microsoft.com/en-us/library/az24scfc.aspx",
    "http://www.developerfusion.com/tools/convert/csharp-to-vb/"
}

Sub Test1()
    sw = System.Diagnostics.Stopwatch.StartNew
    Dim action As Action(Of Object) = Sub(url As String)
                                          Dim wReq As WebRequest = WebRequest.Create(url)
                                          RunWebStreamAsync(wReq, closeEvent)
                                          Console.WriteLine("done in {0} ms : {1}", sw.ElapsedMilliseconds, Left(url, 50))
                                      End Sub
    Try
        '' Cosntruct a started task
        Dim t(5) As task
        For i As Integer = 0 to 5
            t(i) = New Task(action, urls(i)) 
        Next
        For Each tsk In t
            tsk.Start()
        Next
        closeEvent.Set()
        Task.WaitAll(t)
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    End Try
    Console.WriteLine("total in {0} ms", sw.ElapsedMilliseconds)
End Sub

Dim WaitCount As Integer = 6
Sub Test2()
    sw = System.Diagnostics.Stopwatch.StartNew
    For i As Integer = 0 to 5
        StartThread(urls(i))
    Next
    closeEvent.Set()
    Do While WaitCount > 0
        Thread.Sleep(1000)
    Loop
    Console.WriteLine("total in {0} ms", sw.ElapsedMilliseconds)
End Sub

Private Sub StartThread(ByVal url As String)
    Dim trd As Thread = New Thread(AddressOf ThreadTask)
    trd.IsBackground = True
    trd.Start(url)
End Sub

Private Sub ThreadTask(ByVal arg As Object)
    Dim url As String = arg
    Try
        Dim wReq As WebRequest = WebRequest.Create(url)
        RunWebStreamAsync(wReq, closeEvent)
    Catch 
    End Try
    Console.WriteLine("done in {0} ms : {1}", sw.ElapsedMilliseconds, Left(url, 50))
    Interlocked.Decrement(WaitCount)
End Sub

Public Sub RunWebStreamAsync(ByVal wr As WebRequest, ByVal CloseTask As ManualResetEvent)
    Dim hwra As New MyWebRequest
    hwra.LoadAsync(wr)
    Do While hwra.AsyncOperationInProgress
        If CloseTask.WaitOne(1000) = True Then
            If hwra.CancellationPending = False Then
                hwra.CancellationPending = True
                wr.Abort()
            End If
        Else
            Thread.Sleep(100)
        End If
    Loop
End Sub

Class MyWebRequest
    Public Property CancellationPending As Boolean
    Public Property AsyncOperationInProgress As Boolean

    Public Sub LoadAsync(ByVal req As WebRequest)
        AsyncOperationInProgress = True
        ' Invoke BeginGetResponse on a threadpool thread, as it has
        ' unpredictable latency
        Dim bgrd = (New WaitCallback(AddressOf Me.BeginGetResponseDelegate))
        bgrd.BeginInvoke(req, Nothing, Nothing)
    End Sub

    ' Solely for calling BeginGetResponse itself asynchronously. 
    Private Sub BeginGetResponseDelegate(ByVal arg As Object)
        If CancellationPending Then
            PostCompleted(Nothing, True)
        Else
            Dim req As WebRequest = DirectCast(arg, WebRequest)
            req.BeginGetResponse(New AsyncCallback(AddressOf GetResponseCallback), req)
        End If
    End Sub

    Private Sub PostCompleted(ByVal p1 As Object, ByVal p2 As Boolean)
        AsyncOperationInProgress = False
        ' do something
    End Sub

    Private Sub GetResponseCallback(ByVal result As IAsyncResult)
        If CancellationPending Then
            PostCompleted(Nothing, True)
        Else
            Try
                ' Continue on with asynchronous reading.
                PostCompleted(Nothing, True)
            Catch ex As Exception
                ' Since this is on a non-UI thread, we catch any exceptions and
                ' pass them back as data to the UI-thread. 
                PostCompleted(ex, False)
            End Try
        End If
    End Sub
End Class
End Module

1 个答案:

答案 0 :(得分:2)

不同之处在于,您的任务仅在任务计划程序决定应该启动它们时启动 - 而线程全部由您的代码立即启动。

如果你立即取消,下面这段代码是一个繁忙的循环 - 设置CloseTask时,这个循环不包含Sleep - 时间:

  Do While hwra.AsyncOperationInProgress
        If CloseTask.WaitOne(1000) = True Then
            If hwra.CancellationPending = False Then
                hwra.CancellationPending = True
                wr.Abort()
            End If
        Else
            Thread.Sleep(100)
        End If
    Loop

由于此代码循环占用大量CPU周期,因此这将阻止任务调度程序启动新任务 - 这就是为什么任务会串行执行(因此为什么测试需要这么长时间)。

为了提高性能,您可以尝试的一些想法是:

  • 在一个微不足道的层面上,将Sleep置于那个繁忙的循环中。
  • 在调用hwra.LoadAsync(wr)之前检查是否设置了CloseTask事件
  • 确保在设置CloseTask时调用WebRequest.Abort(这在代码示例中暗示,但它没有完成)
  • 重新架构,以便Web请求使用已完成的事件进行响应,而不是使用线程忙于轮询AsyncOperationInProgress标志 - 如果您使用线程来监视每个asyncIO,那么您也可以使用Sync IO。