Поделиться через


Практическое руководство. Прослушивание запросов на отмену, содержащих дескрипторы ожидания

Если метод блокируется на время ожидания сигнала события, он не может проверить значение токена отмены и своевременно отреагировать на него. Первый пример демонстрирует, как эту проблему можно решить для таких событий, как System.Threading.ManualResetEvent, которые не реализуют поддержку унифицированной инфраструктуры отмены. Второй пример демонстрирует более простой подход с применением System.Threading.ManualResetEventSlim, который поддерживает унифицированную отмену.

Примечание.

Если включен параметр "Just My Code", Visual Studio в некоторых случаях будет прерываться в строке, которая создает исключение и отображает сообщение об ошибке, которое говорит "исключение не обрабатывается пользовательским кодом". Эта ошибка является доброкачественной. Вы можете нажать клавишу F5, чтобы продолжить выполнение программы и увидеть поведение системы при обработке этого исключения, которое продемонстрировано в примерах ниже. Чтобы Visual Studio не прерывал выполнение программы после первой ошибки, снимите флажок "Только мой код", последовательно выбрав Сервис, Параметры, Отладка, Общие.

Пример 1

Следующий пример с помощью ManualResetEvent демонстрирует, как разблокировать дескрипторы ожидания, не поддерживающие унифицированную отмену.

using System;
using System.Threading;
using System.Threading.Tasks;

class CancelOldStyleEvents
{
    // Old-style MRE that doesn't support unified cancellation.
    static ManualResetEvent mre = new ManualResetEvent(false);

    static void Main()
    {
        var cts = new CancellationTokenSource();

        // Pass the same token source to the delegate and to the task instance.
        Task.Run(() => DoWork(cts.Token), cts.Token);
        Console.WriteLine("Press s to start/restart, p to pause, or c to cancel.");
        Console.WriteLine("Or any other key to exit.");

        // Old-style UI thread.
        bool goAgain = true;
        while (goAgain)
        {
            char ch = Console.ReadKey(true).KeyChar;

            switch (ch)
            {
                case 'c':
                    cts.Cancel();
                    break;
                case 'p':
                    mre.Reset();
                    break;
                case 's':
                    mre.Set();
                    break;
                default:
                    goAgain = false;
                    break;
            }

            Thread.Sleep(100);
        }
        cts.Dispose();
    }

    static void DoWork(CancellationToken token)
    {
        while (true)
        {
            // Wait on the event if it is not signaled.
            int eventThatSignaledIndex =
                   WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
                                      new TimeSpan(0, 0, 20));

            // Were we canceled while waiting?
            if (eventThatSignaledIndex == 1)
            {
                Console.WriteLine("The wait operation was canceled.");
                throw new OperationCanceledException(token);
            }
            // Were we canceled while running?
            else if (token.IsCancellationRequested)
            {
                Console.WriteLine("I was canceled while running.");
                token.ThrowIfCancellationRequested();
            }
            // Did we time out?
            else if (eventThatSignaledIndex == WaitHandle.WaitTimeout)
            {
                Console.WriteLine("I timed out.");
                break;
            }
            else
            {
                Console.Write("Working... ");
                // Simulating work.
                Thread.SpinWait(5000000);
            }
        }
    }
}
Imports System.Threading
Imports System.Threading.Tasks

Class CancelOldStyleEvents
    ' Old-style MRE that doesn't support unified cancellation.
    Shared mre As New ManualResetEvent(False)

    Shared Sub Main()
        Dim cts As New CancellationTokenSource()

        ' Pass the same token source to the delegate and to the task instance.
        Task.Run(Sub() DoWork(cts.Token), cts.Token)
        Console.WriteLine("Press c to cancel, p to pause, or s to start/restart.")
        Console.WriteLine("Or any other key to exit.")

        ' Old-style UI thread.
        Dim goAgain As Boolean = True
        While goAgain
            Dim ch As Char = Console.ReadKey(True).KeyChar
            Select Case ch
                Case "c"c
                    cts.Cancel()
                Case "p"c
                    mre.Reset()
                Case "s"c
                    mre.Set()
                Case Else
                    goAgain = False
            End Select

            Thread.Sleep(100)
        End While
        cts.Dispose()
    End Sub

    Shared Sub DoWork(ByVal token As CancellationToken)
        While True
            ' Wait on the event if it is not signaled.
            Dim waitHandles() As WaitHandle = {mre, token.WaitHandle}
            Dim eventThatSignaledIndex =
                WaitHandle.WaitAny(waitHandles, _
                                   New TimeSpan(0, 0, 20))

            ' Were we canceled while waiting?
            ' The first If statement is equivalent to
            ' token.ThrowIfCancellationRequested()
            If eventThatSignaledIndex = 1 Then
                Console.WriteLine("The wait operation was canceled.")
                Throw New OperationCanceledException(token)
                ' Were we canceled while running?
            ElseIf token.IsCancellationRequested = True Then
                Console.WriteLine("Cancelling per user request.")
                token.ThrowIfCancellationRequested()
                ' Did we time out?
            ElseIf eventThatSignaledIndex = WaitHandle.WaitTimeout Then
                Console.WriteLine("The wait operation timed out.")
                Exit While
            Else
                ' Simulating work.
                Console.Write("Working... ")
                Thread.SpinWait(5000000)
            End If
        End While
    End Sub
End Class

Пример 2

Следующий пример с помощью ManualResetEventSlim демонстрирует, как разблокировать примитивы координации, поддерживающие унифицированную отмену. Этот же подход можно использовать для других простых примитивов координации, таких как SemaphoreSlim и CountdownEvent.

using System;
using System.Threading;
using System.Threading.Tasks;

class CancelNewStyleEvents
{
   // New-style MRESlim that supports unified cancellation
   // in its Wait methods.
   static ManualResetEventSlim mres = new ManualResetEventSlim(false);

   static void Main()
   {
      var cts = new CancellationTokenSource();

      // Pass the same token source to the delegate and to the task instance.
      Task.Run(() => DoWork(cts.Token), cts.Token);
      Console.WriteLine("Press c to cancel, p to pause, or s to start/restart,");
      Console.WriteLine("or any other key to exit.");

      // New-style UI thread.
         bool goAgain = true;
         while (goAgain)
         {
             char ch = Console.ReadKey(true).KeyChar;

             switch (ch)
             {
                 case 'c':
                     // Token can only be canceled once.
                     cts.Cancel();
                     break;
                 case 'p':
                     mres.Reset();
                     break;
                 case 's':
                     mres.Set();
                     break;
                 default:
                     goAgain = false;
                     break;
             }

             Thread.Sleep(100);
         }
         cts.Dispose();
     }

     static void DoWork(CancellationToken token)
     {

         while (true)
         {
             if (token.IsCancellationRequested)
             {
                 Console.WriteLine("Canceled while running.");
                 token.ThrowIfCancellationRequested();
             }

             // Wait on the event to be signaled
             // or the token to be canceled,
             // whichever comes first. The token
             // will throw an exception if it is canceled
             // while the thread is waiting on the event.
             try
             {
                 // mres is a ManualResetEventSlim
                 mres.Wait(token);
             }
             catch (OperationCanceledException)
             {
                 // Throw immediately to be responsive. The
                 // alternative is to do one more item of work,
                 // and throw on next iteration, because
                 // IsCancellationRequested will be true.
                 Console.WriteLine("The wait operation was canceled.");
                 throw;
             }

             Console.Write("Working...");
             // Simulating work.
             Thread.SpinWait(500000);
         }
     }
 }
Imports System.Threading
Imports System.Threading.Tasks

Class CancelNewStyleEvents

    ' New-style MRESlim that supports unified cancellation
    ' in its Wait methods.
    Shared mres As ManualResetEventSlim = New ManualResetEventSlim(False)

    Shared Sub Main()

        Dim cts As New CancellationTokenSource()

        ' Pass the same token source to the delegate and to the task instance.
        Task.Run(Sub() DoWork(cts.Token), cts.Token)
        Console.WriteLine("Press c to cancel, p to pause, or s to start/restart,")
        Console.WriteLine("or any other key to exit.")

        ' New-style UI thread.
        Dim goAgain As Boolean = True
        While goAgain = True

            Dim ch As Char = Console.ReadKey(True).KeyChar

            Select Case ch
                Case "c"c
                    ' Token can only be canceled once.
                    cts.Cancel()
                Case "p"c
                    mres.Reset()
                Case "s"c
                    mres.Set()
                Case Else
                    goAgain = False
            End Select

            Thread.Sleep(100)
        End While
        cts.Dispose()
    End Sub

    Shared Sub DoWork(ByVal token As CancellationToken)
        While True
            If token.IsCancellationRequested Then
                Console.WriteLine("Canceled while running.")
                token.ThrowIfCancellationRequested()
            End If

            ' Wait on the event to be signaled
            ' or the token to be canceled,
            ' whichever comes first. The token
            ' will throw an exception if it is canceled
            ' while the thread is waiting on the event.
            Try
                ' mres is a ManualResetEventSlim
                mres.Wait(token)
            Catch e As OperationCanceledException
                ' Throw immediately to be responsive. The
                ' alternative is to do one more item of work,
                ' and throw on next iteration, because
                ' IsCancellationRequested will be true.
                Console.WriteLine("Canceled while waiting.")
                Throw
            End Try

            ' Simulating work.
            Console.Write("Working...")
            Thread.SpinWait(500000)
        End While
    End Sub
End Class

См. также