HOW TO:取消 PLINQ 查詢

更新:2010 年 5 月

下列範例說明取消 PLINQ 查詢的兩種方式。 第一個範例說明如何取消大多由資料周遊所組成的查詢。 第二個範例說明如何取消使用者函式在計算時需耗費高度資源的查詢。

注意事項注意事項

啟用 "Just My Code" 時,Visual Studio 會在擲回例外狀況的程式碼行處中斷,並顯示「使用者程式碼未處理的例外狀況」之類的錯誤訊息。這是良性的錯誤。您可以按 F5 從中斷的地方繼續,並看到下面範例所示的例外處理行為。若要防止 Visual Studio 在遇到第一個錯誤時就中斷,只要取消選取 [工具]、[選項]、[偵錯]、[一般] 下的 [Just My Code] 核取方塊即可。

這個範例是為了示範用法,執行速度可能比不上對應的循序 LINQ to Objects 查詢。如需加速的詳細資訊,請參閱認識 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
namespace PLINQCancellation_1
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            int[] source = Enumerable.Range(1, 10000000).ToArray();
            CancellationTokenSource cs = 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(() =>
            {
                UserClicksTheCancelButton(cs);
            });

            int[] results = null;
            try
            {
                results = (from num in source.AsParallel().WithCancellation(cs.Token)
                           where num % 3 == 0
                           orderby num descending
                           select num).ToArray();

            }

            catch (OperationCanceledException e)
            {
                Console.WriteLine(e.Message);
            }

            catch (AggregateException ae)
            {
                if (ae.InnerExceptions != null)
                {
                    foreach (Exception e in ae.InnerExceptions)
                        Console.WriteLine(e.Message);
                }
            }

            if (results != null)
            {
                foreach (var v in results)
                    Console.WriteLine(v);
            }
            Console.WriteLine();
            Console.ReadKey();

        }

        static void UserClicksTheCancelButton(CancellationTokenSource cs)
        {
            // Wait between 150 and 500 ms, then cancel.
            // Adjust these values if necessary to make
            // cancellation fire while query is still executing.
            Random rand = new Random();
            Thread.Sleep(rand.Next(150, 350));
            cs.Cancel();
        }
    }
}

PLINQ 架構不會將單一 OperationCanceledException 彙總成一個 System.AggregateException;OperationCanceledException 必須在個別的 catch 區塊中進行處理。 如果有一個或多個使用者委派擲回 OperationCanceledException(externalCT) (使用外部 System.Threading.CancellationToken),但沒有其他例外狀況,且查詢定義為 AsParallel().WithCancellation(externalCT),則 PLINQ 會發出單一 OperationCanceledException (externalCT),而不是 System.AggregateException。 但如果有一個使用者委派擲回 OperationCanceledException,而另一個委派擲回另一個例外狀況類型,則這兩個例外狀況將會彙總成一個 AggregateException

取消的一般指引如下:

  1. 如果您執行使用者委派取消,則應向 PLINQ 通知有關外部 CancellationToken 的資訊,並擲回 OperationCanceledException (externalCT)。

  2. 如果取消執行,且未擲回其他例外狀況,則您應處理 OperationCanceledException,而不是 AggregateException

下列範例說明如何在使用者程式碼中的函式會高度耗費運算資源時處理取消。

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
namespace PLINQCancellation_2
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {


            int[] source = Enumerable.Range(1, 10000000).ToArray();
            CancellationTokenSource cs = 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(() =>
            {
                UserClicksTheCancelButton(cs);
            });

            double[] results = null;
            try
            {
                results = (from num in source.AsParallel().WithCancellation(cs.Token)
                           where num % 3 == 0
                           select Function(num, cs.Token)).ToArray();

            }


            catch (OperationCanceledException e)
            {
                Console.WriteLine(e.Message);
            }
            catch (AggregateException ae)
            {
                if (ae.InnerExceptions != null)
                {
                    foreach (Exception e in ae.InnerExceptions)
                        Console.WriteLine(e.Message);
                }
            }

            if (results != null)
            {
                foreach (var v in results)
                    Console.WriteLine(v);
            }
            Console.WriteLine();
            Console.ReadKey();

        }

        // A toy method to simulate work.
        static double Function(int n, CancellationToken ct)
        {
            // If work is expected to take longer than 1 ms
            // then try to check cancellation status more
            // often within that work.
            for (int i = 0; i < 5; i++)
            {
                // Work hard for approx 1 millisecond.
                Thread.SpinWait(50000);

                // Check for cancellation request.
                ct.ThrowIfCancellationRequested();
            }
            // Anything will do for our purposes.
            return Math.Sqrt(n);
        }

        static void UserClicksTheCancelButton(CancellationTokenSource cs)
        {
            // Wait between 150 and 500 ms, then cancel.
            // Adjust these values if necessary to make
            // cancellation fire while query is still executing.
            Random rand = new Random();
            Thread.Sleep(rand.Next(150, 350));
            Console.WriteLine("Press 'c' to cancel");
            if (Console.ReadKey().KeyChar == 'c')
                cs.Cancel();
        }
    }
}

當您處理使用者程式碼中的取消時,您無須在查詢定義中使用 WithCancellation<TSource>。 但我們建議您使用此項目,因為 WithCancellation<TSource> 並不會影響查詢效能,並且可讓取消由查詢運算子和您的使用者程式碼來處理。

為確保系統回應,建議您每隔一毫秒左右檢查一次有無取消;但 10 毫秒以內的期間都是可接受的。 此頻率應不會對程式碼的效能造成負面影響。

當列舉程式遭到處置,例如程式碼從逐一查看查詢結果的 foreach (Visual Basic 中為 For Each) 迴圈中斷時,查詢會被取消,但不會擲回例外狀況。

請參閱

參考

ParallelEnumerable

概念

平行 LINQ (PLINQ)

取消

變更記錄

日期

記錄

原因

2010 年 5 月

加入有關使用方式和 加速的比較注意事項。

客戶回函。