Export (0) Print
Expand All

How to: Cancel a PLINQ Query

The following examples show two ways to cancel a PLINQ query. The first example shows how to cancel a query that consists mostly of data traversal. The second example shows how to cancel a query that contains a user function that is computationally expensive.

Note Note

When "Just My Code" is enabled, Visual Studio will break on the line that throws the exception and display an error message that says "exception not handled by user code." This error is benign. You can press F5 to continue from it, and see the exception-handling behavior that is demonstrated in the examples below. To prevent Visual Studio from breaking on the first error, just uncheck the "Just My Code" checkbox under Tools, Options, Debugging, General.

This example is intended to demonstrate usage, and might not run faster than the equivalent sequential LINQ to Objects query. For more information about speedup, see Understanding Speedup in PLINQ.

Class Program
    Private Shared Sub Main(ByVal args As String())
        Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
        Dim cs As New CancellationTokenSource()

        ' Start a new asynchronous task that will cancel the  
        ' operation from another thread. Typically you would call 
        ' Cancel() in response to a button click or some other 
        ' user interface event.
        Task.Factory.StartNew(Sub()
                                  UserClicksTheCancelButton(cs)
                              End Sub)

        Dim results As Integer() = Nothing 
        Try

            results = (From num In source.AsParallel().WithCancellation(cs.Token) _
                Where num Mod 3 = 0 _
                Order By num Descending _
                Select num).ToArray()
        Catch e As OperationCanceledException

            Console.WriteLine(e.Message)
        Catch ae As AggregateException

            If ae.InnerExceptions IsNot Nothing Then 
                For Each e As Exception In ae.InnerExceptions
                    Console.WriteLine(e.Message)
                Next 
            End If 
        End Try 

        If results IsNot Nothing Then 
            For Each item In results
                Console.WriteLine(item)
            Next 
        End If
        Console.WriteLine()

        Console.ReadKey()
    End Sub 

    Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)
        ' Wait between 150 and 500 ms, then cancel. 
        ' Adjust these values if necessary to make 
        ' cancellation fire while query is still executing. 
        Dim rand As New Random()
        Thread.Sleep(rand.[Next](150, 350))
        cs.Cancel()
    End Sub 
End Class

The PLINQ framework does not roll a single OperationCanceledException into an System.AggregateException; the OperationCanceledException must be handled in a separate catch block. If one or more user delegates throws an OperationCanceledException(externalCT) (by using an external System.Threading.CancellationToken) but no other exception, and the query was defined as AsParallel().WithCancellation(externalCT), then PLINQ will issue a single OperationCanceledException (externalCT) rather than an System.AggregateException. However, if one user delegate throws an OperationCanceledException, and another delegate throws another exception type, then both exceptions will be rolled into an AggregateException.

The general guidance on cancellation is as follows:

  1. If you perform user-delegate cancellation you should inform PLINQ about the external CancellationToken and throw an OperationCanceledException(externalCT).

  2. If cancellation occurs and no other exceptions are thrown, then you should handle an OperationCanceledException rather than an AggregateException.

The following example shows how to handle cancellation when you have a computationally expensive function in user code.

Class Program2
    Private Shared Sub Main(ByVal args As String())


        Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
        Dim cs As New CancellationTokenSource()

        ' Start a new asynchronous task that will cancel the  
        ' operation from another thread. Typically you would call 
        ' Cancel() in response to a button click or some other 
        ' user interface event.
        Task.Factory.StartNew(Sub()

                                  UserClicksTheCancelButton(cs)
                              End Sub)

        Dim results As Double() = Nothing 
        Try

            results = (From num In source.AsParallel().WithCancellation(cs.Token) _
                Where num Mod 3 = 0 _
                Select [Function](num, cs.Token)).ToArray()
        Catch e As OperationCanceledException


            Console.WriteLine(e.Message)
        Catch ae As AggregateException
            If ae.InnerExceptions IsNot Nothing Then 
                For Each e As Exception In ae.InnerExceptions
                    Console.WriteLine(e.Message)
                Next 
            End If 
        End Try 

        If results IsNot Nothing Then 
            For Each item In results
                Console.WriteLine(item)
            Next 
        End If
        Console.WriteLine()

        Console.ReadKey()
    End Sub 

    ' A toy method to simulate work. 
    Private Shared Function [Function](ByVal n As Integer, ByVal ct As CancellationToken) As Double 
        ' If work is expected to take longer than 1 ms 
        ' then try to check cancellation status more 
        ' often within that work. 
        For i As Integer = 0 To 4
            ' Work hard for approx 1 millisecond.
            Thread.SpinWait(50000)

            ' Check for cancellation request. 
            If ct.IsCancellationRequested Then 
                Throw New OperationCanceledException(ct)
            End If 
        Next 
        ' Anything will do for our purposes. 
        Return Math.Sqrt(n)
    End Function 

    Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)
        ' Wait between 150 and 500 ms, then cancel. 
        ' Adjust these values if necessary to make 
        ' cancellation fire while query is still executing. 
        Dim rand As New Random()
        Thread.Sleep(rand.[Next](150, 350))
        Console.WriteLine("Press 'c' to cancel")
        If Console.ReadKey().KeyChar = "c"c Then
            cs.Cancel()

        End If 
    End Sub 
End Class

When you handle the cancellation in user code, you do not have to use WithCancellation(Of TSource) in the query definition. However, we recommended that you do this because WithCancellation(Of TSource) has no effect on query performance and it enables the cancellation to be handled by query operators and your user code.

In order to ensure system responsiveness, we recommend that you check for cancellation around once per millisecond; however, any period up to 10 milliseconds is considered acceptable. This frequency should not have a negative impact on your code's performance.

When an enumerator is disposed, for example when code breaks out of a foreach (For Each in Visual Basic) loop that is iterating over query results, then the query is cancelled, but no exception is thrown.

Show:
© 2014 Microsoft