如何终止/取消 TPL 任务?

在一个线程中,我创建一些 System.Threading.Task并开始每个任务。

当我执行 .Abort()来终止线程时,任务不会中止。

如何将 .Abort()传送到我的任务?

190018 次浏览

任务正在 ThreadPool 上执行(至少在使用默认工厂的情况下) ,因此中止线程不会影响任务。有关中止任务,请参见 msdn 上的 任务取消

你不能。任务使用线程池中的后台线程。此外,不建议使用 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();
}
}

任务通过 取消令牌对取消提供一流的支持。使用取消令牌创建任务,并通过这些显式取消任务。

您可以使用 CancellationToken来控制任务是否被取消。你是说在它开始之前中止它(“没关系,我已经这样做了”) ,还是实际上在中间打断它?如果是前者,CancellationToken可能会有帮助; 如果是后者,您可能需要实现自己的“纾困”机制,并在任务执行的适当位置检查您是否应该快速失败(您仍然可以使用取消令牌来帮助您,但它更多的是手动操作)。

MSDN 有一篇关于取消任务的文章: Http://msdn.microsoft.com/en-us/library/dd997396.aspx

你不应该直接这样做。将您的任务设计为使用 取消令牌,并以这种方式取消它们。

此外,我还建议将您的主线程更改为通过 CancelationToken 运行。调用 Thread.Abort()是一个坏主意-它可能导致各种问题,这是非常难以诊断。相反,该线程可以使用相同的 取消,您的任务使用-和相同的 CancellationTokenSource可以用来触发取消 所有的任务和您的主线程。

这将导致一个更简单、更安全的设计。

这类事情是 Abort不被推崇的逻辑原因之一。首先,如果可能的话,不要使用 Thread.Abort()来取消或停止一个线程。 Abort()应该只用于强制终止不能响应及时停止的更和平请求的线程。

也就是说,您需要提供一个共享取消指示器,一个线程设置并等待另一个线程定期检查并优雅地退出。.NET 4包含一个专门为此目的设计的结构,即 CancellationToken

如果捕获运行任务的线程,则很容易中止任务。下面是一个示例代码:

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

我用了“任务”。Run ()来显示最常见的用例——使用 Tasks 的舒适性和旧的单线程代码,这些代码不使用 CcellationTokenSource 类来确定是否应该取消它。

正如 这篇文章所建议的,这可以通过以下方式实现:

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

虽然它可以工作,但是不推荐使用这种方法。如果您可以控制在任务中执行的代码,那么您最好使用正确的取消处理。

我使用混合的方法来取消任务。

  • 首先,我试图用 取消礼貌地取消它。
  • 如果它仍然在运行(例如,由于开发人员的错误) ,那么使用老式的 中止行动方法进行错误操作并杀死它。

看看下面的例子:

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

回答 Prerak K 关于在 Task 中不使用匿名方法时如何使用取消令牌的问题。工厂。StartNew () ,可以将 CcellationToken 作为参数传递给以 StartNew ()开始的方法,如 MSDN 示例 给你所示。

例如:。

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,


}

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

如果可以在自己的线程上创建任务并在其 Thread对象上调用 Abort,则可以中止类似线程的任务。默认情况下,任务在线程池线程或调用线程上运行——通常都不希望中止这两个线程。

若要确保任务获得自己的线程,请创建从 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 TryExecuteTaskInline(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方法。特别是当您调用它来中止当前线程以外的线程时,您不知道在抛出 异常时执行了什么代码或者执行失败了,您也不能确定您的应用程序的状态或者它负责保存的任何应用程序和用户状态。例如,调用 Thread.Abort可能会阻止静态构造函数执行或阻止释放非托管资源。

你可以使用这个类..: 它适用于所有类型的返回值. 。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;


namespace CarNUChargeTester
{
public class TimeOutTaskRunner<T>
{
private Func<T> func;
private int sec;
private T result;
public TimeOutTaskRunner(Func<T> func, int sec)
{
this.func = func;
this.sec = sec;
}


public bool run()
{
var scheduler = new SingleThreadTaskScheduler();
Task<T> task = Task<T>.Factory.StartNew(func, (new CancellationTokenSource()).Token, TaskCreationOptions.LongRunning, scheduler);
if (!task.Wait(TimeSpan.FromSeconds(sec)))
{
scheduler.TaskThread.Abort();
return false;
}
result = task.Result;
return true;
}
public T getResult() { return result; }
}
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();
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException();
}
}

要使用它,你可以写:

TimeOutTaskRunner<string> tr = new TimeOutTaskRunner<string>(f, 10); // 10 sec to run f
if (!tr.run())
errorMsg("TimeOut"); !! My func
tr.getResult() // get the results if it done without timeout..