TPL Görevlerini nasıl iptal edebilir / iptal edebilirim?


156

Bir iş parçacığında, bazı oluşturmak System.Threading.Taskve her görevi başlatmak.

Bir .Abort()iş parçacığı öldürmek için yaptığımda , görevler iptal edilmez.

.Abort()Görevlerime nasıl iletebilirim ?


Yanıtlar:


228

Yapamazsın. Görevler, iş parçacığı havuzundan arka plan iş parçacıkları kullanır. Ayrıca İptal yöntemini kullanarak evrelerin iptal edilmesi önerilmez. İptal jetonlarını kullanarak görevleri iptal etmenin uygun bir yolunu açıklayan aşağıdaki blog gönderisine bakabilirsiniz . İşte bir örnek:

class Program
{
    static void Main()
    {
        var ts = new CancellationTokenSource();
        CancellationToken ct = ts.Token;
        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                // do some heavy work here
                Thread.Sleep(100);
                if (ct.IsCancellationRequested)
                {
                    // another thread decided to cancel
                    Console.WriteLine("task canceled");
                    break;
                }
            }
        }, ct);

        // Simulate waiting 3s for the task to complete
        Thread.Sleep(3000);

        // Can't wait anymore => cancel this task 
        ts.Cancel();
        Console.ReadLine();
    }
}

5
Güzel açıklama. Bir sorum var, Task.Factory.StartNew'de anonim bir yöntem olmadığında nasıl çalışır? Task.Factory.StartNew (() => ProcessMyMethod (),
cancelmentToken

61
yürütme görevi içinde geri dönmeyen bir engelleme çağrısı varsa ne olur?
mehmet6parmak

3
@ mehmet6parmak O zaman yapabileceğiniz tek şey Task.Wait(TimeSpan / int)dışarıdan (zamana dayalı) bir son tarih vermek için kullanmaktır .
Mark

2
Yeni bir içindeki yöntemlerin yürütülmesini yönetmek için özel sınıfım varsa ne olur Task? Gibi bir şey: public int StartNewTask(Action method). StartNewTaskYöntemin içinde yeni bir Task:: Task task = new Task(() => method()); task.Start();. Peki nasıl yönetebilirim CancellationToken? Ayrıca Threadhala asılı olan bazı Görevler olup olmadığını kontrol etmek için bir mantık uygulamam gerekip gerekmediğini bilmek istiyorum Form.Closing. İle Threadskullandığım Thread.Abort().
Cheshire Cat

Aman tanrım, ne kötü bir örnek! Basit bir boolean koşulu, tabii ki ilk denemek istiyorum! Ancak, ayrı bir Görevde sona erdirmek için çok zaman alabileceğim bir fonksiyonum var ve ideal olarak bir diş çekme veya herhangi bir şey hakkında hiçbir şey bilmemelidir. Peki, tavsiyenizle işlevi nasıl iptal edebilirim?
Hi-Melek

32

Görevin çalıştığı iş parçacığını yakalarsanız, bir Görevi iptal etmek kolayca mümkündür. İşte bunu göstermek için bir örnek kod:

void Main()
{
    Thread thread = null;

    Task t = Task.Run(() => 
    {
        //Capture the thread
        thread = Thread.CurrentThread;

        //Simulate work (usually from 3rd party code)
        Thread.Sleep(1000);

        //If you comment out thread.Abort(), then this will be displayed
        Console.WriteLine("Task finished!");
    });

    //This is needed in the example to avoid thread being still NULL
    Thread.Sleep(10);

    //Cancel the task by aborting the thread
    thread.Abort();
}

Bunun için en yaygın kullanım durumunu göstermek için Task.Run () yöntemini kullandım.


2
Bu fikir için teşekkürler. Bu yaklaşımı, CancellationTokendesteği olmayan bazı dış kodlar için bir zaman aşımı uygulamak için kullandı ...
Christoph Fink

7
AFAIK thread.abort sizi yığınız hakkında bilinmeyecek, geçersiz olabilir. Hiç denemedim ama thread.abort kaydetmek olacak ayrı uygulama etki alanında bir iş parçacığı başlangıç ​​sanırım! Ayrıca, tek bir görevi iptal etmek için çözümünüzde bütün bir iş parçacığı boşa gider. Öncelikle görevleri değil, iş parçacıklarını kullanmanız gerekir. (downvote)
Martin Meeser

1
Yazdığım gibi - bu çözüm, belirli koşullar altında düşünülebilecek son çözümdür. Elbette CancellationToken, yarış gerektirmeyen ücretsiz ve hatta daha basit çözümler dikkate alınmalıdır. Yukarıdaki kod, kullanım alanını değil, yalnızca yöntemi göstermektedir.
Florian Rappl

8
Bu yaklaşımın bilinmeyen sonuçları olabileceğini düşünüyorum ve bunu üretim kodunda tavsiye etmem. Görevlerin her zaman farklı bir iş parçacığında yürütülmesi garanti edilmez, yani zamanlayıcı karar verirse bunları oluşturan iş parçacığında çalıştırılabilir (ana iş parçacığının threadyerel değişkene geçirileceği anlamına gelir ). Kodunuzda, gerçekten istediğiniz şey olmayan ana iş parçacığını iptal edebilirsiniz. Belki ipliğin iptal edilmeden önce aynı olup olmadığını kontrol etmek, eğer iptal etmekte ısrar ederseniz, iyi bir düşünmek olacaktır
Ivaylo Slavov

10
@Martin - Bu soruyu SO hakkında araştırdığım için, Thread.Abort'u görevleri öldürmek için kullanan birkaç cevabı aşağıya indirdiğinizi görüyorum, ancak alternatif çözümler sunmadınız. İptal işlemini desteklemeyen ve bir Görevde çalışan 3. taraf kodunu nasıl öldürebilirsiniz ?
Gordon Bean

32

Gibi bu yayını da anlaşılacağı, bu şu şekilde yapılabilir:

int Foo(CancellationToken token)
{
    Thread t = Thread.CurrentThread;
    using (token.Register(t.Abort))
    {
        // compute-bound work here
    }
}

Çalışmasına rağmen, bu yaklaşımı kullanmanız önerilmez. Görevde çalışan kodu kontrol edebiliyorsanız, iptal işleminin doğru şekilde gerçekleştirilmesi daha iyi olur.


6
Geri dönüşlerini belirtirken farklı bir yaklaşım verdiği için +1. Bunun yapılabileceğini bilmiyordum :)
Joel

1
Çözüm için teşekkürler! Bir şekilde thread örneğini yöntemden almak ve bu örneği doğrudan iptal etmek yerine jetonu yönteme geçirebilir ve jeton kaynağını iptal edebiliriz.
Sam

Görevin iptal edilen iş parçacığı bir iş parçacığı havuzu iş parçacığı veya arayanın görevi çağıran iş parçacığı olabilir. Bundan kaçınmak için, görev için ayrılmış bir iş parçacığı belirtmek üzere bir TaskScheduler kullanabilirsiniz .
Edward Brey

19

Bu tür şeyler Abort, kullanımdan kaldırılmanın lojistik nedenlerinden biridir . Her şeyden önce, mümkünse bir konuyu iptal etmek veya durdurmak için kullanmayın Thread.Abort(). Abort()yalnızca daha huzurlu taleplere zamanında yanıt vermeyen bir konuyu zorla öldürmek için kullanılmalıdır.

Bununla birlikte, bir iş parçacığının periyodik olarak kontrol edip zarif bir şekilde çıkarken bir iş parçacığının ayarladığı ve beklediği bir paylaşılan iptal göstergesi sağlamanız gerekir. .NET 4, bu amaç için özel olarak tasarlanmış bir yapı içerir CancellationToken.


8

Bunu doğrudan yapmaya çalışmamalısınız. Görevlerinizi bir CancellationToken ile çalışacak şekilde tasarlayın ve bu şekilde iptal edin.

Ayrıca, ana iş parçacığınızı bir CancellationToken aracılığıyla işlev görecek şekilde değiştirmenizi tavsiye ederim. Arama Thread.Abort()kötü bir fikirdir - teşhis edilmesi çok zor olan çeşitli sorunlara yol açabilir. Bunun yerine, bu iş parçacığı görevlerinizin kullandığı İptal işlemini kullanabilir ve aynı CancellationTokenSourceişlem tüm görevlerinizin ve ana iş parçacığınızın iptalini tetiklemek için kullanılabilir .

Bu çok daha basit ve daha güvenli bir tasarıma yol açacaktır.


8

Prerak K'nin Task.Factory.StartNew () öğesinde anonim bir yöntem kullanmadığınızda CancellationTokens'i kullanma hakkındaki sorusunu yanıtlamak için, CancellationToken'i MSDN örneğinde gösterildiği gibi StartNew () ile başlattığınız yönteme parametre olarak iletirsiniz. burada .

Örneğin

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

Task.Factory.StartNew( () => DoSomeWork(1, token), token);

static void DoSomeWork(int taskNum, CancellationToken ct)
{
    // Do work here, checking and acting on ct.IsCancellationRequested where applicable, 

}

7

Bir görevi iptal etmek için karışık bir yaklaşım kullanıyorum.

  • Öncelikle, kullanarak kibarca İptal çalışıyorum İptali .
  • Hala çalışıyorsa (örneğin, bir geliştiricinin hatası nedeniyle), eski bir Abort yöntemini kullanarak yanlış davranın ve öldürün .

Aşağıdaki örneği inceleyin:

private CancellationTokenSource taskToken;
private AutoResetEvent awaitReplyOnRequestEvent = new AutoResetEvent(false);

void Main()
{
    // Start a task which is doing nothing but sleeps 1s
    LaunchTaskAsync();
    Thread.Sleep(100);
    // Stop the task
    StopTask();
}

/// <summary>
///     Launch task in a new thread
/// </summary>
void LaunchTaskAsync()
{
    taskToken = new CancellationTokenSource();
    Task.Factory.StartNew(() =>
        {
            try
            {   //Capture the thread
                runningTaskThread = Thread.CurrentThread;
                // Run the task
                if (taskToken.IsCancellationRequested || !awaitReplyOnRequestEvent.WaitOne(10000))
                    return;
                Console.WriteLine("Task finished!");
            }
            catch (Exception exc)
            {
                // Handle exception
            }
        }, taskToken.Token);
}

/// <summary>
///     Stop running task
/// </summary>
void StopTask()
{
    // Attempt to cancel the task politely
    if (taskToken != null)
    {
        if (taskToken.IsCancellationRequested)
            return;
        else
            taskToken.Cancel();
    }

    // Notify a waiting thread that an event has occurred
    if (awaitReplyOnRequestEvent != null)
        awaitReplyOnRequestEvent.Set();

    // If 1 sec later the task is still running, kill it cruelly
    if (runningTaskThread != null)
    {
        try
        {
            runningTaskThread.Join(TimeSpan.FromSeconds(1));
        }
        catch (Exception ex)
        {
            runningTaskThread.Abort();
        }
    }
}

4

Görevler, iptal belirteçleri aracılığıyla iptal için birinci sınıf desteğe sahiptir . İptal belirteçleriyle görevlerinizi oluşturun ve bunları açıkça kullanarak iptal edin.


4

CancellationTokenGörevin iptal edilip edilmeyeceğini kontrol etmek için a kullanabilirsiniz . Başlamadan önce ("nevermind, bunu zaten yaptım") veya aslında ortadan yarıda kesilmekten mi bahsediyorsun? Eğer eski ise, yardımcı CancellationTokenolabilir; ikincisi, muhtemelen kendi "kefalet" mekanizmanızı uygulamanız ve görev yürütmedeki uygun noktalarda hızlı başarısız olup olmadığınızı kontrol etmeniz gerekecektir (yine de size yardımcı olması için CancellationToken'i kullanabilirsiniz, ancak biraz daha manueldir).

MSDN'nin Görevleri iptal etme hakkında bir makalesi vardır: http://msdn.microsoft.com/en-us/library/dd997396.aspx


3

Görev ThreadPool'da yürütülüyor (en azından varsayılan fabrika kullanıyorsanız), iş parçacığının iptal edilmesi görevleri etkileyemez. Görevleri iptal etmek için, bkz . Msdn'deki Görev İptal .


1

Denedim CancellationTokenSourceama yapamam. Ve bunu kendi yöntemimle yaptım. Ve çalışıyor.

namespace Blokick.Provider
{
    public class SignalRConnectProvider
    {
        public SignalRConnectProvider()
        {
        }

        public bool IsStopRequested { get; set; } = false; //1-)This is important and default `false`.

        public async Task<string> ConnectTab()
        {
            string messageText = "";
            for (int count = 1; count < 20; count++)
            {
                if (count == 1)
                {
                //Do stuff.
                }

                try
                {
                //Do stuff.
                }
                catch (Exception ex)
                {
                //Do stuff.
                }
                if (IsStopRequested) //3-)This is important. The control of the task stopping request. Must be true and in inside.
                {
                    return messageText = "Task stopped."; //4-) And so return and exit the code and task.
                }
                if (Connected)
                {
                //Do stuff.
                }
                if (count == 19)
                {
                //Do stuff.
                }
            }
            return messageText;
        }
    }
}

Ve yöntemi çağıran başka bir sınıf:

namespace Blokick.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MessagePerson : ContentPage
    {
        SignalRConnectProvider signalR = new SignalRConnectProvider();

        public MessagePerson()
        {
            InitializeComponent();

            signalR.IsStopRequested = true; // 2-) And this. Make true if running the task and go inside if statement of the IsStopRequested property.

            if (signalR.ChatHubProxy != null)
            {
                 signalR.Disconnect();
            }

            LoadSignalRMessage();
        }
    }
}

0

Görevin kendi iş parçacığında oluşturulmasına Abortve Threadnesnesinin çağrılmasına neden olabilirseniz, iş parçacığı gibi bir görevi iptal edebilirsiniz . Varsayılan olarak, bir görev iş parçacığı havuzu iş parçacığı veya çağıran iş parçacığı üzerinde çalışır - her ikisi de genellikle iptal etmek istediğiniz.

Görevin kendi iş parçacığını almasını sağlamak için, türetilmiş özel bir zamanlayıcı oluşturun TaskScheduler. Uygulamanızda QueueTaskyeni bir iş parçacığı oluşturun ve bunu görevi yürütmek için kullanın. Daha sonra, görevin hatalı bir durumda bir ile tamamlanmasına neden olacak iş parçacığını iptal edebilirsiniz ThreadAbortException.

Bu görev zamanlayıcıyı kullanın:

class SingleThreadTaskScheduler : TaskScheduler
{
    public Thread TaskThread { get; private set; }

    protected override void QueueTask(Task task)
    {
        TaskThread = new Thread(() => TryExecuteTask(task));
        TaskThread.Start();
    }

    protected override IEnumerable<Task> GetScheduledTasks() => throw new NotSupportedException(); // Unused
    protected override bool NotSupportedException(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException(); // Unused
}

Görevinize şu şekilde başlayın:

var scheduler = new SingleThreadTaskScheduler();
var task = Task.Factory.StartNew(action, cancellationToken, TaskCreationOptions.LongRunning, scheduler);

Daha sonra, aşağıdakileri iptal edebilirsiniz:

scheduler.TaskThread.Abort();

Bir ipliğin iptal edilmesine ilişkin uyarının hala geçerli olduğuna dikkat edin :

Thread.AbortYöntem, dikkatli bir şekilde kullanılmalıdır. Özellikle geçerli iş parçacığı dışında bir iş parçacığını iptal etmek için çağırdığınızda, ThreadAbortException özel durumu atıldığında hangi kodun yürütüldüğünü veya yürütülemediğini bilmiyorsunuz , ayrıca uygulamanızın durumundan veya herhangi bir uygulamanın ve kullanıcı durumundan da emin olamıyorsunuz. korumaktan sorumlu olduğunu. Örneğin, çağrı Thread.Abortstatik kurucuların yürütülmesini engelleyebilir veya yönetilmeyen kaynakların serbest bırakılmasını engelleyebilir.


Bu kod bir çalışma zamanı istisnasıyla başarısız olur: System.InvalidOperationException: Önceden başlatılmış bir görevde RunSynchronously çağrılamayabilir.
Theodor Zoulias

1
@TheodorZoulias İyi yakala. Teşekkürler. Kodu düzelttim ve genellikle cevabı geliştirdim.
Edward Brey

1
Evet, bu hatayı düzeltti. Muhtemelen belirtilmesi gereken bir diğer uyarı Thread.Abort, .NET Core'da desteklenmemesidir. Orada kullanmaya çalışmak bir istisnayla sonuçlanır: System.PlatformNotSupportedException: İş parçacığı iptali bu platformda desteklenmiyor. Üçüncü bir uyarı, söz verilen SingleThreadTaskSchedulergörevlerle, yani asyncdelegelerle oluşturulan görevlerle etkili bir şekilde kullanılamamasıdır . Örneğin, gömülü bir await Task.Delay(1000)iş parçacığı olmadan çalışır, bu nedenle iş parçacığı olaylarından etkilenmez.
Theodor Zoulias
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.