方法: PLINQ クエリを取り消すHow to: Cancel a PLINQ Query

次の例は、PLINQ クエリを取り消す 2 つの方法を示しています。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. 2 つ目の例は、負荷の大きいユーザー関数を含むクエリを取り消す方法を示しています。The second example shows how to cancel a query that contains a user function that is computationally expensive.

注意

[マイ コードのみ] が有効になっている場合、Visual Studio では、例外をスローする行で処理が中断され、"ユーザー コードで処理されない例外" に関するエラー メッセージが表示されます。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. F5 キーを押して、処理が中断された箇所から続行し、以下の例に示す例外処理動作を確認できます。You can press F5 to continue from it, and see the exception-handling behavior that is demonstrated in the examples below. Visual Studio による処理が最初のエラーで中断しないようにするには、[ツール] メニューの [オプション]、[デバッグ] 、[全般] の順にクリックし、[マイ コードのみ] チェック ボックスをオフにします。To prevent Visual Studio from breaking on the first error, just uncheck the "Just My Code" checkbox under Tools, Options, Debugging, General.

この例は、使用方法を示すことを意図したものであるため、同等の順次的な LINQ to Objects クエリほど高速ではない可能性があります。This example is intended to demonstrate usage, and might not run faster than the equivalent sequential LINQ to Objects query. 高速化の詳細については、「PLINQ での高速化について」を参照してください。For more information about speedup, see Understanding Speedup in PLINQ.

Example

namespace PLINQCancellation_1
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using static System.Console;

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

            int[] results = null;
            try
            {
                results = (from num in source.AsParallel().WithCancellation(cts.Token)
                           where num % 3 == 0
                           orderby num descending
                           select num).ToArray();
            }
            catch (OperationCanceledException e)
            {
                WriteLine(e.Message);
            }
            catch (AggregateException ae)
            {
                if (ae.InnerExceptions != null)
                {
                    foreach (Exception e in ae.InnerExceptions)
                        WriteLine(e.Message);
                }
            }
            finally
            {
               cts.Dispose();
            }

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

        static void UserClicksTheCancelButton(CancellationTokenSource cts)
        {
            // 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, 500));
            cts.Cancel();
        }
    }
}
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
        Finally
            cs.Dispose()
        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

PLINQ フレームワークでは単一の OperationCanceledExceptionSystem.AggregateException にローリングされません。OperationCanceledException は別個のキャッチ ブロックで処理する必要があります。The PLINQ framework does not roll a single OperationCanceledException into an System.AggregateException; the OperationCanceledException must be handled in a separate catch block. 1 つ以上のユーザー デリゲートが (外部の System.Threading.CancellationToken を使用して) OperationCanceledException(externalCT) をスローし、他の例外はスローしない場合で、クエリが AsParallel().WithCancellation(externalCT) として定義されている場合は、PLINQ は System.AggregateException ではなく、単一の OperationCanceledException (externalCT) を発行します。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. ただし、1 つのユーザー デリゲートが OperationCanceledException をスローし、別のデリゲートが別の種類の例外をスローした場合、両方の例外が 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. ユーザー デリゲートを取り消す場合、外部の CancellationToken について PLINQ に通知し、OperationCanceledException(externalCT) をスローする必要があります。If you perform user-delegate cancellation you should inform PLINQ about the external CancellationToken and throw an OperationCanceledException(externalCT).

  2. 取り消しが発生し、その他の例外がスローされない場合は、AggregateException ではなく OperationCanceledException を処理する必要があります。If cancellation occurs and no other exceptions are thrown, then you should handle an OperationCanceledException rather than an AggregateException.

Example

次の例は、ユーザー コードで負荷が大きい関数を使用する場合の取り消しの処理方法を示しています。The following example shows how to handle cancellation when you have a computationally expensive function in user code.

namespace PLINQCancellation_2
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using static System.Console;

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

            double[] results = null;
            try
            {
                results = (from num in source.AsParallel().WithCancellation(cts.Token)
                           where num % 3 == 0
                           select Function(num, cts.Token)).ToArray();
            }
            catch (OperationCanceledException e)
            {
                WriteLine(e.Message);
            }
            catch (AggregateException ae)
            {
                if (ae.InnerExceptions != null)
                {
                    foreach (Exception e in ae.InnerExceptions)
                        WriteLine(e.Message);
                }
            }
            finally
            {
                cts.Dispose();
            }

            if (results != null)
            {
                foreach (var v in results)
                    WriteLine(v);
            }
            WriteLine();
            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 cts)
        {
            // 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, 500));
            WriteLine("Press 'c' to cancel");
            if (ReadKey().KeyChar == 'c')
                cts.Cancel();
        }
    }
}
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
        Finally
            cs.Dispose()
        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

ユーザー コードで取り消しを処理する場合、クエリ定義で WithCancellation を使用する必要はありません。When you handle the cancellation in user code, you do not have to use WithCancellation in the query definition. ただし、WithCancellation がクエリのパフォーマンスに影響を与えることはなく、クエリ演算子とユーザー コードで取り消しを処理できるため、このようにすることをお勧めします。However, we recommended that you do this because WithCancellation has no effect on query performance and it enables the cancellation to be handled by query operators and your user code.

システムの応答性を確保できるように、ミリ秒単位で取り消しを確認することをお勧めします。ただし、許容可能と見なされるのは 10 ミリ秒までです。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.

列挙子が破棄された場合、たとえば、クエリ結果を反復処理している foreach (Visual Basic では For Each) ループからコードが抜け出た場合、クエリは取り消されますが、例外はスローされません。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.

関連項目See also