开发者

Why cancelling 6 WebRequest using Task.Start takes longer that Thread.Start

开发者 https://www.devze.com 2023-03-22 23:55 出处:网络
I have custom asynchronous WebRequest class that I am testing to find out how fast the request will be cancelled and found some odd results, that starting 6 WebRequests with TPL and cancelling them ri

I have custom asynchronous WebRequest class that I am testing to find out how fast the request will be cancelled and found some odd results, that starting 6 WebRequests with TPL and cancelling them right away takes 25 sec, but when I used just regular background threads it took only 5 sec.

Update: Running them without cancelling takes with Task.Start 9 sec and Thread.Start 3 sec accordingly.

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


The difference is due to the fact that your Tasks are only started when the Task scheduler decides it should start them - while the Threads are all started immediately by your code.

In the case where you cancel immediately, this code below is a busy loop - when CloseTask is set, then this loop contains no Sleep-time:

  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

Since this code loops taking lots of CPU cycles, then this will discourage the Task scheduler from starting new Tasks - so that's why the tasks get executed in series (hence why your test takes so long).

To improve perfromance, some ideas you could try are:

  • at a trivial level, put a Sleep in that busy loop.
  • check if the CloseTask event is set before calling hwra.LoadAsync(wr)
  • ensure that WebRequest.Abort is called when CloseTask is set (this is hinted at in your code sample, but it's not complete)
  • rearchitect so that the web requests respond back using completed events instead of using threads to busy poll the AsyncOperationInProgress flag - if you are using a thread to monitor each asyncIO, then you may as well just use Sync IO.
0

精彩评论

暂无评论...
验证码 换一张
取 消