TPLタスクを中止/キャンセルするにはどうすればよいですか?


156

スレッドでは、いくつかを作成してSystem.Threading.Task各タスクを開始します。

.Abort()スレッドを強制終了するためにa を実行しても、タスクは中止されません。

どうすれば.Abort()自分のタスクに送信できますか?


回答:


228

できません。タスクはスレッドプールのバックグラウンドスレッドを使用します。また、Abortメソッドを使用してスレッドをキャンセルすることはお勧めしません。キャンセルトークンを使用してタスクをキャンセルする適切な方法を説明している次のブログ投稿をご覧ください。次に例を示します。

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
いい説明。質問があります。Task.Factory.StartNewに匿名メソッドがない場合、どのように機能しますか?Task.Factory.StartNew(()=> ProcessMyMethod()、cancellationToken)のように
Prera​​k K

61
実行中のタスク内に戻らないブロッキング呼び出しがある場合はどうなりますか?
mehmet6parmak 14年

3
@ mehmet6parmak私ができる唯一のことはTask.Wait(TimeSpan / int)、外部から(時間ベースの)期限を与えることです。
マーク

2
新しい内部のメソッドの実行を管理するカスタムクラスがある場合はどうなりTaskますか?ような何か:public int StartNewTask(Action method)StartNewTaskメソッド内では、次の方法で新しいを作成TaskしますTask task = new Task(() => method()); task.Start();。では、どうすれば管理できCancellationTokenますか?Threadまだハングしているタスクがあるかどうかをチェックするロジックを実装する必要があるかどうかを知りたいので、タスクを強制終了しますForm.Closing。使っThreadsてますThread.Abort()
Cheshire Cat

なんと悪い例でしょう!これは単純なブール条件ですが、もちろん最初に試すものです!しかし、つまり、別のタスクに関数があり、終了するまでに時間がかかる可能性があります。理想的には、スレッド化などについては何も知らないはずです。それで、あなたのアドバイスで関数をどのようにキャンセルしますか?
Hi-Angel

32

タスクが実行されているスレッドをキャプチャすると、タスクの中止が簡単に可能になります。これを示すサンプルコードを次に示します。

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

私はTask.Run()を使用して、この最も一般的な使用例を示しました。CancellationTokenSourceクラスを使用せずにキャンセルする必要があるかどうかを判断しない古いシングルスレッドコードでTasksの快適さを使用しています。


2
このアイデアをありがとう。このアプローチを使用して、CancellationTokenサポートされていない外部コードのタイムアウトを実装しました...
Christoph Fink

7
AFAIK thread.abortはスタックを知らないままにします、それは無効かもしれません。私は試したことはありませんが、別のアプリドメインでスレッドを開始すると、thread.abortは保存されます。その上、1つのタスクを中止するだけのために、ソリューション全体のスレッドが無駄になります。最初にタスクを使用する必要はなく、スレッドを使用します。(反対票)
Martin Meeser 2014年

1
私が書いたように-この解決策は、特定の状況下で検討される可能性がある最後の手段です。もちろん、CancellationToken競合状態のない、より簡単なソリューションを検討する必要があります。上記のコードは、使用方法の領域ではなく、メソッドのみを示しています。
Florian Rappl 2014年

8
このアプローチは未知の結果をもたらす可能性があると思うので、量産コードでは推奨しません。タスクは必ずしも別のスレッドで実行されるとは限りません。つまり、スケジューラが決定した場合、タスクを作成したスレッドと同じスレッドで実行できます(つまり、メインスレッドがthreadローカル変数に渡されます)。あなたのコードでは、メインスレッドをアボートすることになるかもしれませんが、それはあなたが本当に望んでいることではありません。おそらく、中止を主張する場合、中止前にスレッドが同じであるかどうかを確認することをお勧めします。
Ivaylo Slavov

10
@Martin-SOでこの質問を調査したところ、Thread.Abortを使用してタスクを強制終了するいくつかの回答に反対票を投じたようですが、代替ソリューションを提供していません。キャンセルをサポートせず、タスクで実行されているサードパーティのコードをどのように殺すことができますか?
Gordon Bean

32

同じように、この記事が示唆する、これは次のように行うことができます。

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

動作しますが、そのようなアプローチを使用することは推奨されません。タスクで実行されるコードを制御できる場合は、キャンセルを適切に処理することをお勧めします。


6
フォールバックを述べながら別のアプローチを与えるための+1。これができるとは知りませんでした:)
Joel

1
解決策をありがとう!なんらかの方法でスレッドインスタンスを取得してこのインスタンスを直接中止するのではなく、トークンをメソッドに渡してトークンソースをキャンセルするだけです。
Sam

中止されるタスクのスレッドは、スレッドプールスレッド、またはタスクを呼び出す呼び出し元のスレッドの場合があります。これを回避するには、TaskSchedulerを使用して、タスク専用のスレッドを指定します
エドワードブレイ

19

このようなことは、Abortが廃止される理由の1つです。何よりもまず、可能な限りスレッドをキャンセルまたは停止するために使用しないくださいThread.Abort() Abort()タイムリーに停止するというより平和的な要求に応答していないスレッドを強制的に強制終了するためにのみ使用してください。

とはいえ、他のスレッドが定期的にチェックして正常に終了する間、1つのスレッドが設定して待機する共有キャンセルインジケーターを提供する必要があります。.NET 4には、この目的のために特別に設計された構造が含まれていCancellationTokenます。


8

これを直接行わないでください。CancellationTokenと連携するようにタスクを設計するこの方法でキャンセルします。

さらに、CancellationTokenを介して機能するようにメインスレッドを変更することもお勧めします。呼び出しThread.Abort()は悪い考えです-診断するのが非常に難しいさまざまな問題を引き起こす可能性があります。代わりに、そのスレッドは、タスクが使用するのと同じキャンセルを使用できます。また、同じスレッドを使用してCancellationTokenSourceすべてのタスクとメインスレッドのキャンセルをトリガーできます。

これにより、はるかにシンプルで安全な設計が可能になります。


8

Task.Factory.StartNew()で匿名メソッドを使用しない場合のCancellationTokensの使用方法に関するPrera​​k Kの質問に回答するには、MSDNの例に示すように、Startlation()で開始するメソッドにパラメーターとしてCancellationTokenを渡します。ここに

例えば

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

混合アプローチを使用してタスクをキャンセルします。

  • まず、私が使って丁寧にキャンセルしようとしているキャンセルを
  • それがまだ実行中の場合(たとえば、開発者のミスが原因)、動作がおかしくなり、旧式のAbortメソッドを使用して強制終了します。

以下の例を確認してください。

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

を使用しCancellationTokenて、タスクをキャンセルするかどうかを制御できます。それが始まる前に中止することについて話しているのですか(「気にしないでください、私はすでにこれをしました」)、または実際に途中で中断することですか?前者の場合、CancellationTokenが役立ちます。後者の場合は、おそらく独自の「ベイルアウト」メカニズムを実装し、タスク実行の適切な時点で迅速に失敗するかどうかを確認する必要があります(CancellationTokenを使用して支援することもできますが、少し手作業です)。

MSDNには、タスクのキャンセルに関する記事があります。http: //msdn.microsoft.com/en-us/library/dd997396.aspx



1

試しましたCancellationTokenSourceができません。そして私は自分のやり方でこれをやった。そしてそれは機能します。

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

そして、メソッドを呼び出す別のクラス:

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

あなたは、タスクが独自のスレッドと呼び出しで作成されることがあります場合は、スレッドのようなタスクを中止することができAbort、その上Threadオブジェクトます。デフォルトでは、タスクはスレッドプールスレッドまたは呼び出し元のスレッドで実行されますが、どちらも通常は中止する必要はありません。

タスクが独自のスレッドを取得するようにするには、から派生したカスタムスケジューラを作成しますTaskScheduler。の実装でQueueTask、新しいスレッドを作成し、それを使用してタスクを実行します。後で、スレッドを中止できます。これにより、タスクがで障害状態で完了しThreadAbortExceptionます。

このタスクスケジューラを使用します。

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
}

次のようにタスクを開始します。

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

後で、次のように中止できます:

scheduler.TaskThread.Abort();

スレッドの中止に関する警告が引き続き適用されることに注意してください。

このThread.Abortメソッドは注意して使用する必要があります。特に、現在のスレッド以外のスレッドを中止するためにそれを呼び出す場合、ThreadAbortExceptionがスローされたときに実行されたコードまたは実行に失敗したコードがわかりません。また、アプリケーションの状態またはアプリケーションとユーザーの状態を確認することもできません。保存する責任があること。たとえば、呼び出しThread.Abortにより、静的コンストラクターの実行が妨げられたり、アンマネージリソースの解放が妨げられたりする可能性があります。


このコードは実行時例外で失敗します。System.InvalidOperationException:RunSynchronouslyは、既に開始されているタスクで呼び出されない可能性があります。
Theodor Zoulias

1
@TheodorZoulias良いキャッチ。ありがとう。私はコードを修正し、一般的に答えを改善しました。
エドワードブレイ

1
うん、これはバグを修正しました。おそらく言及すべきもう1つの警告は、Thread.Abort.NET Coreではサポートされていないことです。これを使用しようとすると、例外が発生します。System.PlatformNotSupportedException:スレッドの中止は、このプラットフォームではサポートされていません。3つ目の注意点は、SingleThreadTaskSchedulerpromiseスタイルのタスク、つまりasyncデリゲートで作成されたタスクではを効果的に使用できないことです。たとえば、埋め込みawait Task.Delay(1000)はスレッドなしで実行されるため、スレッドイベントの影響を受けません。
Theodor Zoulias
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.