Bagaimana cara saya membatalkan / membatalkan Tugas TPL?

Jawaban:

228

Kamu tidak bisa Tugas menggunakan utas latar belakang dari kumpulan utas. Juga membatalkan utas menggunakan metode Abort tidak dianjurkan. Anda dapat melihat posting blog berikut yang menjelaskan cara yang tepat untuk membatalkan tugas menggunakan token pembatalan. Ini sebuah contoh:

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();
    }
}
Darin Dimitrov
sumber
5
Penjelasan yang bagus. Saya punya pertanyaan, bagaimana cara kerjanya ketika kita tidak memiliki metode anonim di Task.Factory.StartNew? seperti Task.Factory.StartNew (() => ProcessMyMethod (), cancellationToken)
Prerak K
61
bagaimana jika ada panggilan pemblokiran yang tidak kembali dalam tugas eksekusi?
mehmet6parmak
3
@ mehmet6parmak Saya pikir satu-satunya hal yang dapat Anda lakukan adalah menggunakannya Task.Wait(TimeSpan / int)untuk memberikan batas waktu (berdasarkan waktu) dari luar.
Tandai
2
Bagaimana jika saya memiliki kelas khusus saya untuk mengelola pelaksanaan metode di dalam yang baru Task? Sesuatu seperti: public int StartNewTask(Action method). Di dalam StartNewTaskmetode yang saya membuat yang baru Taskdengan: Task task = new Task(() => method()); task.Start();. Jadi bagaimana saya bisa mengelola CancellationToken? Saya juga ingin tahu jika ingin Threadmenerapkan logika untuk memeriksa apakah ada beberapa Tugas yang masih menggantung dan bunuh mereka kapan Form.Closing. Dengan Threadssaya gunakan Thread.Abort().
Cheshire Cat
Omg, contoh yang buruk! Ini adalah kondisi boolean yang sederhana, tentu saja itu yang pertama dicoba! Tetapi saya memiliki fungsi dalam Tugas terpisah, yang mungkin membutuhkan banyak waktu untuk diakhiri, dan idealnya tidak perlu tahu apa-apa tentang threading atau apa pun. Jadi bagaimana cara saya membatalkan fungsi dengan saran Anda?
Hi-Angel
32

Membatalkan tugas dapat dilakukan dengan mudah jika Anda menangkap utas tempat tugas berjalan. Berikut adalah contoh kode untuk menunjukkan ini:

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

Saya menggunakan Task.Run () untuk menunjukkan use-case yang paling umum untuk ini - menggunakan kenyamanan Tasks dengan kode single-threaded lama, yang tidak menggunakan kelas PembatalanTokenSource untuk menentukan apakah harus dibatalkan atau tidak.

Florian Rappl
sumber
2
Terima kasih atas ide ini. Menggunakan pendekatan ini untuk menerapkan batas waktu untuk beberapa kode eksternal, yang tidak memiliki CancellationTokendukungan ...
Christoph Fink
7
AFAIK thread.abort akan membuat Anda tidak mengetahui tentang tumpukan Anda, itu mungkin tidak valid. Saya belum pernah mencobanya tetapi saya kira memulai utas di domain aplikasi terpisah agar utas.abort akan disimpan! Selain itu, seluruh utas terbuang dalam solusi Anda hanya untuk membatalkan satu tugas. Anda tidak harus menggunakan tugas tetapi utas di tempat pertama. (downvote)
Martin Meeser
1
Seperti yang saya tulis - solusi ini adalah pilihan terakhir yang dapat dipertimbangkan dalam keadaan tertentu. Tentu saja solusi CancellationTokenatau bahkan lebih sederhana yang bebas kondisi ras harus dipertimbangkan. Kode di atas hanya menggambarkan metode, bukan bidang penggunaan.
Florian Rappl
8
Saya pikir pendekatan ini mungkin memiliki konsekuensi yang tidak diketahui dan saya tidak akan merekomendasikannya dalam kode produksi. Tugas tidak selalu dijamin untuk dieksekusi di utas yang berbeda, artinya tugas tersebut dapat dijalankan pada utas yang sama dengan yang dibuat jika penjadwal memutuskan demikian (yang berarti utas utama akan diteruskan ke threadvariabel lokal). Dalam kode Anda, Anda mungkin akhirnya membatalkan utas utama, yang sebenarnya bukan yang Anda inginkan. Mungkin sebuah cek apakah
utasnya
10
@ Martin - Ketika saya telah meneliti pertanyaan ini pada SO, saya melihat Anda telah memilih beberapa jawaban yang menggunakan Thread.Abort untuk membunuh tugas, tetapi Anda belum memberikan solusi alternatif apa pun. Bagaimana Anda bisa membunuh kode pihak ke-3 yang tidak mendukung pembatalan dan sedang menjalankan tugas ?
Gordon Bean
32

Seperti yang disarankan pos ini , ini dapat dilakukan dengan cara berikut:

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

Meskipun berhasil, tidak disarankan untuk menggunakan pendekatan seperti itu. Jika Anda dapat mengontrol kode yang menjalankan tugas, Anda sebaiknya menggunakan penanganan pembatalan yang benar.

starteleport
sumber
6
+1 untuk memberikan pendekatan yang berbeda sambil menyatakan fallbacknya. Saya tidak tahu ini bisa dilakukan :)
Joel
1
Terima kasih atas solusinya! Kami hanya bisa meneruskan token ke metode dan membatalkan tokenource, alih-alih mendapatkan instance thread dari metode dan membatalkan instance ini secara langsung.
Sam
Utas tugas yang dibatalkan mungkin berupa utas utas atau utas penelepon yang menjalankan tugas. Untuk menghindari ini, Anda dapat menggunakan Penjadwal Tugas untuk menentukan utas khusus untuk tugas tersebut .
Edward Brey
19

Hal semacam ini adalah salah satu alasan logistik mengapa Abortusang. Pertama dan terutama, jangan gunakan Thread.Abort()untuk membatalkan atau menghentikan utas jika memungkinkan. Abort()seharusnya hanya digunakan untuk secara paksa membunuh utas yang tidak menanggapi permintaan yang lebih damai untuk berhenti tepat waktu.

Karena itu, Anda perlu memberikan indikator pembatalan bersama yang satu set dan tunggu sementara thread lainnya secara berkala memeriksa dan keluar dengan anggun. .NET 4 mencakup struktur yang dirancang khusus untuk tujuan ini, the CancellationToken.

Adam Robinson
sumber
8

Anda seharusnya tidak mencoba melakukan ini secara langsung. Rancang tugas Anda untuk bekerja dengan Pembatalan , dan batalkan dengan cara ini.

Selain itu, saya akan merekomendasikan untuk mengubah utas utama Anda agar berfungsi melalui CancellingToken juga. Memanggil Thread.Abort()adalah ide yang buruk - itu dapat menyebabkan berbagai masalah yang sangat sulit untuk didiagnosis. Alih-alih, utas itu dapat menggunakan Pembatalan yang sama dengan yang digunakan tugas Anda - dan yang sama CancellationTokenSourcedapat digunakan untuk memicu pembatalan semua tugas Anda dan utas utama Anda.

Ini akan mengarah pada desain yang jauh lebih sederhana, dan lebih aman.

Reed Copsey
sumber
8

Untuk menjawab pertanyaan Prerak K tentang cara menggunakan CancurnToken ketika tidak menggunakan metode anonim di Task.Factory.StartNew (), Anda meneruskan CancellingToken sebagai parameter ke dalam metode yang Anda mulai dengan StartNew (), seperti yang ditunjukkan dalam contoh MSDN di sini .

misalnya

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, 

}
Jools
sumber
7

Saya menggunakan pendekatan campuran untuk membatalkan tugas.

  • Pertama, saya mencoba untuk Membatalkannya dengan sopan menggunakan Pembatalan .
  • Jika masih berjalan (mis. Karena kesalahan pengembang), maka berperilaku salah dan membunuhnya menggunakan metode Abort sekolah lama .

Lihat contoh di bawah ini:

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();
        }
    }
}
Alex Klaus
sumber
4

Tugas memiliki dukungan kelas satu untuk pembatalan melalui token pembatalan . Buat tugas Anda dengan token pembatalan, dan batalkan tugas melalui ini secara eksplisit.

Tim Lloyd
sumber
4

Anda dapat menggunakan a CancellationTokenuntuk mengontrol apakah tugas dibatalkan. Apakah Anda berbicara tentang membatalkannya sebelum dimulai ("tidak pernah, saya sudah melakukan ini"), atau benar-benar memotongnya di tengah? Jika yang pertama, CancellationTokenbisa bermanfaat; jika yang terakhir, Anda mungkin perlu menerapkan mekanisme "bail out" Anda sendiri dan memeriksa pada titik yang sesuai dalam pelaksanaan tugas apakah Anda harus gagal dengan cepat (Anda masih dapat menggunakan CancellingToken untuk membantu Anda, tetapi ini sedikit lebih manual).

MSDN memiliki artikel tentang pembatalan Tugas: http://msdn.microsoft.com/en-us/library/dd997396.aspx

Gulungan
sumber
3

Tugas sedang dieksekusi di ThreadPool (setidaknya, jika Anda menggunakan pabrik default), sehingga membatalkan utas tidak dapat memengaruhi tugas. Untuk tugas yang dibatalkan, lihat Pembatalan Tugas di msdn.

Oliver Hanappi
sumber
1

Saya mencoba CancellationTokenSourcetetapi saya tidak bisa melakukan ini. Dan saya melakukan ini dengan cara saya sendiri. Dan itu berhasil.

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

Dan kelas lain yang memanggil metode:

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();
        }
    }
}
Hasan Tuna Oruç
sumber
0

Anda dapat membatalkan tugas seperti benang jika Anda dapat menyebabkan tugas yang akan dibuat pada thread sendiri dan memanggil Abortpada perusahaan Threadobjek. Secara default, tugas dijalankan pada utas kumpulan utas atau utas panggilan - yang tidak ingin Anda batalkan.

Untuk memastikan tugas mendapatkan utasnya sendiri, buat penjadwal khusus yang berasal dari TaskScheduler. Dalam implementasi Anda QueueTask, buat utas baru dan gunakan untuk menjalankan tugas. Kemudian, Anda dapat membatalkan utas, yang akan menyebabkan tugas selesai dalam keadaan rusak dengan a ThreadAbortException.

Gunakan penjadwal tugas ini:

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
}

Mulai tugas Anda seperti ini:

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

Kemudian, Anda dapat membatalkan dengan:

scheduler.TaskThread.Abort();

Perhatikan bahwa peringatan tentang pembatalan utas masih berlaku:

The Thread.Abortmetode harus digunakan dengan hati-hati. Khususnya ketika Anda menyebutnya untuk membatalkan utas selain utas saat ini, Anda tidak tahu kode apa yang telah dieksekusi atau gagal dijalankan ketika ThreadAbortException dilemparkan, Anda juga tidak bisa memastikan keadaan aplikasi Anda atau aplikasi apa pun dan negara pengguna bahwa itu bertanggung jawab untuk melestarikan. Misalnya, panggilan Thread.Abortdapat mencegah konstruktor statis dari mengeksekusi atau mencegah pelepasan sumber daya yang tidak dikelola.

Edward Brey
sumber
Kode ini gagal dengan pengecualian runtime: System.InvalidOperationException: RunSynchronously mungkin tidak dipanggil pada tugas yang sudah dimulai.
Theodor Zoulias
1
@TheodorZoulias Tangkapan yang bagus. Terima kasih. Saya memperbaiki kode dan umumnya memperbaiki jawabannya.
Edward Brey
1
Ya, ini memperbaiki bug. Peringatan lain yang mungkin harus disebutkan adalah yang Thread.Aborttidak didukung pada .NET Core. Mencoba menggunakannya di sana menghasilkan pengecualian: System.PlatformNotSupportedException: Thread abort tidak didukung pada platform ini. Peringatan ketiga adalah bahwa SingleThreadTaskSchedulertidak dapat digunakan secara efektif dengan tugas gaya janji, dengan kata lain dengan tugas yang dibuat dengan asyncdelegasi. Misalnya tertanam await Task.Delay(1000)berjalan di utas, jadi tidak terpengaruh menjadi acara utas.
Theodor Zoulias