Yönetilen İş Parçacıklarında İptal

.NET Framework 4'den başlayarak .NET, zaman uyumsuz veya uzun süre çalışan zaman uyumlu işlemlerin işbirliğiyle iptali için birleşik bir model kullanır. Bu model, iptal belirteci olarak adlandırılan basit bir nesneyi temel alır. Örneğin yeni iş parçacıkları veya görevler oluşturarak bir veya daha fazla iptal edilebilir işlemi çağıran nesne, belirteci her işleme geçirir. Tek tek işlemler de belirtecin kopyalarını diğer işlemlere geçirebilir. Daha sonra belirteci oluşturan nesne, işlemlerin yaptıklarını durdurmasını istemek için bunu kullanabilir. İptal isteğini yalnızca istekte bulunan nesne verebilir ve her dinleyici isteği fark edip uygun ve zamanında yanıtlamaktan sorumludur.

İşbirlikçi iptal modelini uygulamaya yönelik genel düzen şu şekildedir:

  • Tek tek iptal belirteçlerini yöneten ve iptal bildirimi gönderen bir CancellationTokenSource nesne örneği oluşturma.

  • özelliği tarafından CancellationTokenSource.Token döndürülen belirteci iptali dinleyen her göreve veya iş parçacığına geçirin.

  • İptale yanıt vermek için her görev veya iş parçacığı için bir mekanizma sağlayın.

  • İptal bildirimini CancellationTokenSource.Cancel sağlamak için yöntemini çağırın.

Önemli

CancellationTokenSource sınıfı, IDisposable arabirimini uygular. İptal belirteci kaynağını kullanarak içerdiği yönetilmeyen kaynakları serbest bırakınca yöntemini çağırdığınızdan CancellationTokenSource.Dispose emin olmalısınız.

Aşağıdaki çizimde belirteç kaynağı ile belirtecinin tüm kopyaları arasındaki ilişki gösterilmektedir.

CancellationTokenSource and cancellation tokens

İşbirliğine dayalı iptal modeli, iptale duyarlı uygulamalar ve kitaplıklar oluşturmayı kolaylaştırır ve aşağıdaki özellikleri destekler:

  • İptal işbirlikçidir ve dinleyiciye zorlanmaz. Dinleyici, bir iptal isteğine yanıt olarak düzgün bir şekilde sonlandırmayı belirler.

  • İstekte bulunmak, dinlemeden farklıdır. İptal edilebilir işlemi çağıran bir nesne, iptal işleminin ne zaman (varsa) istendiğini denetleyebilir.

  • İstekte bulunan nesne, yalnızca bir yöntem çağrısı kullanarak iptal isteğini belirtecin tüm kopyalarına düzenler.

  • Dinleyici, birden çok belirteci tek bir bağlı belirteçte birleştirerek aynı anda dinleyebilir.

  • Kullanıcı kodu kitaplık kodundan gelen iptal isteklerini fark edip yanıtlayabilir ve kitaplık kodu da kullanıcı kodundan gelen iptal isteklerini fark edip yanıtlayabilir.

  • Dinleyicilere yoklama, geri çağırma kaydı veya bekleme tanıtıcılarını bekleme yoluyla iptal istekleri bildirilebilir.

İptal Türleri

İptal çerçevesi, aşağıdaki tabloda listelenen bir dizi ilgili tür olarak uygulanır.

Tür adı Açıklama
CancellationTokenSource bir iptal belirteci oluşturan ve ayrıca bu belirtecin tüm kopyaları için iptal isteği veren nesne.
CancellationToken Basit değer türü genellikle yöntem parametresi olarak bir veya daha fazla dinleyiciye geçirilir. Dinleyiciler yoklama, geri çağırma veya bekleme tutamacını kullanarak belirtecin özelliğinin değerini IsCancellationRequested izler.
OperationCanceledException Bu özel durumun oluşturucusunun aşırı yüklemeleri parametre olarak kabul CancellationToken eder. Dinleyiciler isteğe bağlı olarak bu özel durumu oluşturarak iptal kaynağını doğrulayabilir ve iptal isteğine yanıt verdiğini başkalarına bildirebilir.

İptal modeli çeşitli türlerde .NET ile tümleştirilmiştir. En önemlileri , System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult> ve System.Linq.ParallelEnumerable'lerdirSystem.Threading.Tasks.Parallel. Bu işbirliğine dayalı iptal modelini tüm yeni kitaplık ve uygulama kodu için kullanmanızı öneririz.

Kod Örneği

Aşağıdaki örnekte, istekte bulunan nesne bir CancellationTokenSource nesnesi oluşturur ve ardından özelliğini iptal edilebilir işleme geçirir Token . İsteği alan işlem yoklama yaparak belirtecin özelliğinin değerini IsCancellationRequested izler. Değeri olduğunda truedinleyici uygun şekilde sonlandırabilir. Bu örnekte, yöntemi yalnızca çıkar, çoğu durumda gerekli olan tek şey budur.

Not

Örnek, QueueUserWorkItem işbirliğine dayalı iptal çerçevesinin eski API'lerle uyumlu olduğunu göstermek için yöntemini kullanır. Tercih edilen System.Threading.Tasks.Task türü kullanan bir örnek için bkz . Nasıl yapılır: Görevi ve Alt Öğelerini İptal Etme.

using System;
using System.Threading;

public class Example
{
    public static void Main()
    {
        // Create the token source.
        CancellationTokenSource cts = new CancellationTokenSource();

        // Pass the token to the cancelable operation.
        ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);
        Thread.Sleep(2500);

        // Request cancellation.
        cts.Cancel();
        Console.WriteLine("Cancellation set in token source...");
        Thread.Sleep(2500);
        // Cancellation should have happened, so call Dispose.
        cts.Dispose();
    }

    // Thread 2: The listener
    static void DoSomeWork(object? obj)
    {
        if (obj is null)
            return;

        CancellationToken token = (CancellationToken)obj;

        for (int i = 0; i < 100000; i++)
        {
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("In iteration {0}, cancellation has been requested...",
                                  i + 1);
                // Perform cleanup if necessary.
                //...
                // Terminate the operation.
                break;
            }
            // Simulate some work.
            Thread.SpinWait(500000);
        }
    }
}
// The example displays output like the following:
//       Cancellation set in token source...
//       In iteration 1430, cancellation has been requested...
Imports System.Threading

Module Example
    Public Sub Main()
        ' Create the token source.
        Dim cts As New CancellationTokenSource()

        ' Pass the token to the cancelable operation.
        ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoSomeWork), cts.Token)
        Thread.Sleep(2500)

        ' Request cancellation by setting a flag on the token.
        cts.Cancel()
        Console.WriteLine("Cancellation set in token source...")
        Thread.Sleep(2500)
        ' Cancellation should have happened, so call Dispose.
        cts.Dispose()
    End Sub

    ' Thread 2: The listener
    Sub DoSomeWork(ByVal obj As Object)
        Dim token As CancellationToken = CType(obj, CancellationToken)

        For i As Integer = 0 To 1000000
            If token.IsCancellationRequested Then
                Console.WriteLine("In iteration {0}, cancellation has been requested...",
                                  i + 1)
                ' Perform cleanup if necessary.
                '...
                ' Terminate the operation.
                Exit For
            End If

            ' Simulate some work.
            Thread.SpinWait(500000)
        Next
    End Sub
End Module
' The example displays output like the following:
'       Cancellation set in token source...
'       In iteration 1430, cancellation has been requested...

İşlem İptali ile Nesne İptali Karşılaştırması

İşbirliğine dayalı iptal çerçevesinde iptal, nesneleri değil işlemleri ifade eder. İptal isteği, gerekli temizleme gerçekleştirildikten sonra işlemin en kısa sürede durdurulması gerektiği anlamına gelir. Bir iptal belirteci bir "iptal edilebilir işleme" başvurmalıdır, ancak bu işlem programınızda uygulanabilir. Belirtecin IsCancellationRequested özelliği olarak ayarlandıktan truesonra olarak sıfırlanamaz false. Bu nedenle iptal belirteçleri iptal edildikten sonra yeniden kullanılamaz.

Bir nesne iptal mekanizmasına ihtiyacınız varsa, aşağıdaki örnekte gösterildiği gibi yöntemini çağırarak CancellationToken.Register bunu işlem iptal mekanizmasına dayandırabilirsiniz.

using System;
using System.Threading;

class CancelableObject
{
    public string id;

    public CancelableObject(string id)
    {
        this.id = id;
    }

    public void Cancel()
    {
        Console.WriteLine("Object {0} Cancel callback", id);
        // Perform object cancellation here.
    }
}

public class Example1
{
    public static void Main()
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

        // User defined Class with its own method for cancellation
        var obj1 = new CancelableObject("1");
        var obj2 = new CancelableObject("2");
        var obj3 = new CancelableObject("3");

        // Register the object's cancel method with the token's
        // cancellation request.
        token.Register(() => obj1.Cancel());
        token.Register(() => obj2.Cancel());
        token.Register(() => obj3.Cancel());

        // Request cancellation on the token.
        cts.Cancel();
        // Call Dispose when we're done with the CancellationTokenSource.
        cts.Dispose();
    }
}
// The example displays the following output:
//       Object 3 Cancel callback
//       Object 2 Cancel callback
//       Object 1 Cancel callback
Imports System.Threading

Class CancelableObject
    Public id As String

    Public Sub New(id As String)
        Me.id = id
    End Sub

    Public Sub Cancel()
        Console.WriteLine("Object {0} Cancel callback", id)
        ' Perform object cancellation here.
    End Sub
End Class

Module Example
    Public Sub Main()
        Dim cts As New CancellationTokenSource()
        Dim token As CancellationToken = cts.Token

        ' User defined Class with its own method for cancellation
        Dim obj1 As New CancelableObject("1")
        Dim obj2 As New CancelableObject("2")
        Dim obj3 As New CancelableObject("3")

        ' Register the object's cancel method with the token's
        ' cancellation request.
        token.Register(Sub() obj1.Cancel())
        token.Register(Sub() obj2.Cancel())
        token.Register(Sub() obj3.Cancel())

        ' Request cancellation on the token.
        cts.Cancel()
        ' Call Dispose when we're done with the CancellationTokenSource.
        cts.Dispose()
    End Sub
End Module
' The example displays output like the following:
'       Object 3 Cancel callback
'       Object 2 Cancel callback
'       Object 1 Cancel callback

Bir nesne birden fazla eşzamanlı iptal edilebilir işlemi destekliyorsa, her ayrı iptal edilebilir işleme giriş olarak ayrı bir belirteç geçirin. Bu şekilde, bir işlem diğerlerini etkilemeden iptal edilebilir.

İptal İsteklerini Dinleme ve Yanıtlama

Kullanıcı temsilcisinde, iptal edilebilir bir işlemin uygulayıcısı, iptal isteğine yanıt olarak işlemin nasıl sonlandırileceğini belirler. Çoğu durumda, kullanıcı temsilcisi gerekli temizleme işlemlerini gerçekleştirebilir ve hemen geri dönebilir.

Ancak, daha karmaşık durumlarda, kullanıcı temsilcisinin iptalin gerçekleştiğini kitaplık koduna bildirmesi gerekebilir. Böyle durumlarda, işlemi sonlandırmanın doğru yolu temsilcinin , yöntemini çağırmasıdır ThrowIfCancellationRequestedve bu da bir OperationCanceledException oluşturulur. Kitaplık kodu, kullanıcı temsilcisi iş parçacığında bu özel durumu yakalayabilir ve özel durumun işbirliğine dayalı iptali mi yoksa başka bir istisna durumunu mu gösterdiğini belirlemek için özel durumun belirtecini inceleyebilir.

Task sınıfı bu şekilde işlerOperationCanceledException. Daha fazla bilgi için bkz . Görev İptali.

Yoklama ile Dinleme

Döngü veya özyinelemeli uzun süre çalışan hesaplamalar için, özelliğin değerini CancellationToken.IsCancellationRequested düzenli aralıklarla yoklayarak iptal isteğini dinleyebilirsiniz. Değeri ise true, yöntemin olabildiğince hızlı bir şekilde temizlenmesi ve sonlandırılması gerekir. Yoklamanın en uygun sıklığı, uygulama türüne bağlıdır. Belirli bir program için en iyi yoklama sıklığını belirlemek geliştiriciye bağlıdır. Yoklamanın kendisi performansı önemli ölçüde etkilemez. Aşağıdaki örnekte yoklamanın olası bir yolu gösterilmektedir.

static void NestedLoops(Rectangle rect, CancellationToken token)
{
   for (int col = 0; col < rect.columns && !token.IsCancellationRequested; col++) {
      // Assume that we know that the inner loop is very fast.
      // Therefore, polling once per column in the outer loop condition
      // is sufficient.
      for (int row = 0; row < rect.rows; row++) {
         // Simulating work.
         Thread.SpinWait(5_000);
         Console.Write("{0},{1} ", col, row);
      }
   }

   if (token.IsCancellationRequested) {
      // Cleanup or undo here if necessary...
      Console.WriteLine("\r\nOperation canceled");
      Console.WriteLine("Press any key to exit.");

      // If using Task:
      // token.ThrowIfCancellationRequested();
   }
}
Shared Sub NestedLoops(ByVal rect As Rectangle, ByVal token As CancellationToken)
    Dim col As Integer
    For col = 0 To rect.columns - 1
        ' Assume that we know that the inner loop is very fast.
        ' Therefore, polling once per column in the outer loop condition
        ' is sufficient.
        For col As Integer = 0 To rect.rows - 1
            ' Simulating work.
            Thread.SpinWait(5000)
            Console.Write("0',1' ", x, y)
        Next
    Next

    If token.IsCancellationRequested = True Then
        ' Cleanup or undo here if necessary...
        Console.WriteLine(vbCrLf + "Operation canceled")
        Console.WriteLine("Press any key to exit.")

        ' If using Task:
        ' token.ThrowIfCancellationRequested()
    End If
End Sub

Daha eksiksiz bir örnek için bkz . Nasıl yapılır: Yoklama ile İptal İsteklerini Dinleme.

Geri Çağırma Kaydederek Dinleme

Bazı işlemler iptal belirtecinin değerini zamanında denetleyemeyecek şekilde engellenebilir. Bu durumlarda, bir iptal isteği alındığında yöntemin engelini kaldıran bir geri çağırma yöntemi kaydedebilirsiniz.

yöntemi, Register bu amaç için özel olarak kullanılan bir CancellationTokenRegistration nesne döndürür. Aşağıdaki örnekte, zaman uyumsuz bir Web isteğini iptal etmek için yönteminin nasıl kullanılacağı Register gösterilmektedir.

using System;
using System.Net;
using System.Threading;

class Example4
{
    static void Main()
    {
        CancellationTokenSource cts = new CancellationTokenSource();

        StartWebRequest(cts.Token);

        // cancellation will cause the web
        // request to be cancelled
        cts.Cancel();
    }

    static void StartWebRequest(CancellationToken token)
    {
        WebClient wc = new WebClient();
        wc.DownloadStringCompleted += (s, e) => Console.WriteLine("Request completed.");

        // Cancellation on the token will
        // call CancelAsync on the WebClient.
        token.Register(() =>
        {
            wc.CancelAsync();
            Console.WriteLine("Request cancelled!");
        });

        Console.WriteLine("Starting request.");
        wc.DownloadStringAsync(new Uri("http://www.contoso.com"));
    }
}
Imports System.Net
Imports System.Threading

Class Example
    Private Shared Sub Main()
        Dim cts As New CancellationTokenSource()

        StartWebRequest(cts.Token)

        ' cancellation will cause the web 
        ' request to be cancelled
        cts.Cancel()
    End Sub

    Private Shared Sub StartWebRequest(token As CancellationToken)
        Dim wc As New WebClient()
        wc.DownloadStringCompleted += Function(s, e) Console.WriteLine("Request completed.")

        ' Cancellation on the token will 
        ' call CancelAsync on the WebClient.
        token.Register(Function()
                           wc.CancelAsync()
                           Console.WriteLine("Request cancelled!")

                       End Function)

        Console.WriteLine("Starting request.")
        wc.DownloadStringAsync(New Uri("http://www.contoso.com"))
    End Sub
End Class

CancellationTokenRegistration nesnesi iş parçacığı eşitlemesini yönetir ve geri çağırmanın belirli bir noktada yürütülmesini durdurmasını sağlar.

Sistemin yanıt verme hızını sağlamak ve kilitlenmeleri önlemek için geri çağırmaları kaydederken aşağıdaki yönergelere uyulmalıdır:

  • Geri çağırma yöntemi hızlı olmalıdır çünkü zaman uyumlu olarak çağrılır ve bu nedenle çağrısı geri çağırma dönene Cancel kadar döndürmez.

  • Geri arama çalışırken çağırırsanız Dispose ve geri çağırmanın beklediği bir kilidi tutarsanız, programınız kilitlenmeye neden olabilir. Geri döndükten sonra Dispose , geri çağırma için gereken tüm kaynakları serbest getirebilirsiniz.

  • Geri çağırmalar, bir geri çağırmada el ile iş parçacığı veya SynchronizationContext kullanım gerçekleştirmemelidir. Bir geri çağırmanın belirli bir iş parçacığında çalışması gerekiyorsa, hedef syncContext öğesinin System.Threading.CancellationTokenRegistration etkin SynchronizationContext.Currentolduğunu belirtmenize olanak tanıyan oluşturucuyu kullanın. Geri çağırmada el ile iş parçacığı oluşturmak kilitlenmeye neden olabilir.

Daha eksiksiz bir örnek için bkz . Nasıl yapılır: İptal İstekleri için Geri Çağırmaları Kaydetme.

Bekleme Tutamacı Kullanarak Dinleme

İptal edilebilir bir işlem veya System.Threading.Semaphoregibi System.Threading.ManualResetEvent bir eşitleme temel öğesini beklerken engelleyebilirse, hem olay hem de iptal isteğinde işlemin beklemesini sağlamak için özelliğini kullanabilirsinizCancellationToken.WaitHandle. İptal belirtecinin bekleme tutamacı bir iptal isteğine yanıt olarak işaretlenir ve yöntem, sinyali veren iptal belirteci olup olmadığını belirlemek için yönteminin dönüş değerini WaitAny kullanabilir. İşlem daha sonra yalnızca çıkış yapabilir veya uygun şekilde bir OperationCanceledExceptionoluşturabilir.

// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
       WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
                          new TimeSpan(0, 0, 20));
' 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))

System.Threading.ManualResetEventSlim ve System.Threading.SemaphoreSlim her ikisi de kendi yöntemlerinde iptal çerçevesini Wait destekler. yöntemine geçirebilirsiniz CancellationToken ve iptal istendiğinde olay uyanır ve bir OperationCanceledExceptionoluşturur.

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);
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)

Daha eksiksiz bir örnek için bkz . Nasıl yapılır: Bekleme Tutamaçlarına Sahip İptal İsteklerini Dinleme.

Birden Çok Belirteci Aynı Anda Dinleme

Bazı durumlarda dinleyicinin aynı anda birden çok iptal belirtecini dinlemesi gerekebilir. Örneğin, iptal edilebilir bir işlemin bir yöntem parametresine bağımsız değişken olarak dışarıdan geçirilen bir belirtece ek olarak bir iç iptal belirtecini izlemesi gerekebilir. Bunu başarmak için, aşağıdaki örnekte gösterildiği gibi iki veya daha fazla belirteci tek bir belirteçte birleştirebilen bağlı bir belirteç kaynağı oluşturun.

public void DoWork(CancellationToken externalToken)
{
    // Create a new token that combines the internal and external tokens.
    this.internalToken = internalTokenSource.Token;
    this.externalToken = externalToken;

    using (CancellationTokenSource linkedCts =
            CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
    {
        try
        {
            DoWorkInternal(linkedCts.Token);
        }
        catch (OperationCanceledException)
        {
            if (internalToken.IsCancellationRequested)
            {
                Console.WriteLine("Operation timed out.");
            }
            else if (externalToken.IsCancellationRequested)
            {
                Console.WriteLine("Cancelling per user request.");
                externalToken.ThrowIfCancellationRequested();
            }
        }
    }
}
Public Sub DoWork(ByVal externalToken As CancellationToken)
    ' Create a new token that combines the internal and external tokens.
    Dim internalToken As CancellationToken = internalTokenSource.Token
    Dim linkedCts As CancellationTokenSource =
    CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken)
    Using (linkedCts)
        Try
            DoWorkInternal(linkedCts.Token)
        Catch e As OperationCanceledException
            If e.CancellationToken = internalToken Then
                Console.WriteLine("Operation timed out.")
            ElseIf e.CancellationToken = externalToken Then
                Console.WriteLine("Canceled by external token.")
                externalToken.ThrowIfCancellationRequested()
            End If
        End Try
    End Using
End Sub

İşiniz bittiğinde bağlı belirteç kaynağını çağırmanız Dispose gerektiğine dikkat edin. Daha eksiksiz bir örnek için bkz . Nasıl yapılır: Birden Çok İptal İsteğini Dinleme.

Kitaplık Kodu ile Kullanıcı Kodu Arasında İşbirliği

Birleşik iptal çerçevesi, kitaplık kodunun kullanıcı kodunu iptal etmelerini ve kullanıcı kodunun kitaplık kodunu işbirliği içinde iptal etmelerini mümkün kılar. Sorunsuz işbirliği, aşağıdaki yönergeleri izleyerek her iki tarafa da bağlıdır:

  • Kitaplık kodu iptal edilebilir işlemler sağlıyorsa, kullanıcı kodunun iptal isteğinde bulunabilmesi için dış iptal belirtecini kabul eden genel yöntemler de sağlaması gerekir.

  • Kitaplık kodu kullanıcı koduna çağrıda bulunursa, kitaplık kodu operationCanceledException(externalToken) işlemini işbirliğine dayalı iptal olarak yorumlamalı ve hata özel durumu olarak yorumlanmamalıdır.

  • Kullanıcı temsilcileri, kitaplık kodundan gelen iptal isteklerine zamanında yanıt vermeyi denemelidir.

System.Threading.Tasks.Task ve System.Linq.ParallelEnumerable bu yönergeleri izleyen sınıflara örnektir. Daha fazla bilgi için bkz . Görev İptali ve Nasıl yapılır: PLINQ Sorgusunu İptal Etme.

Ayrıca bkz.