可以像中止一个Thread(Thread.Abort方法)一样中止一个Task吗?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了可以像中止一个Thread(Thread.Abort方法)一样中止一个Task吗?相关的知识,希望对你有一定的参考价值。
我们可以中止这样的线程:
Thread thread = new Thread(SomeMethod);
.
.
.
thread.Abort();
但是我可以以同样的方式中止任务(在.Net 4.0中)而不是取消机制。我想立即杀死任务。
- 你不应该使用Thread.Abort()
- 任务可以取消但不会中止。
Thread.Abort()方法(严重)已被弃用。
线程和任务在停止时应该合作,否则您将面临使系统处于不稳定/未定义状态的风险。
如果您确实需要运行进程并从外部终止它,唯一安全的选择是在单独的AppDomain中运行它。
不使用线程中止的指导是有争议的。我认为仍有一席之地,但在特殊情况下。但是,您应该始终尝试围绕它进行设计并将其视为最后的手段。
例;
您有一个简单的Windows窗体应用程序连接到阻塞同步Web服务。在其中,它在并行循环内的Web服务上执行函数。
CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
Parallel.ForEach(iListOfItems, po, (item, loopState) =>
{
Thread.Sleep(120000); // pretend web service call
});
比如在这个例子中,阻塞调用需要2分钟才能完成。现在我将MaxDegreeOfParallelism设置为ProcessorCount。 iListOfItems中有1000个要处理的项目。
用户单击进程按钮并开始循环,我们在iListOfItems集合中对1000个项目执行“最多”20个线程。每次迭代都在自己的线程上执行。每个线程在Parallel.ForEach创建时都将使用前台线程。这意味着无论主应用程序关闭,app域都将保持活动状态,直到所有线程都完成。
但是,用户需要关闭应用程序,因为他们关闭了表单。这20个线程将继续执行,直到处理完所有1000个项目。在这种情况下,这并不理想,因为应用程序不会像用户期望的那样退出并将继续在幕后运行,这可以通过查看任务管理器来看出。
假设用户再次尝试重建应用程序(VS 2010),它报告exe已锁定,那么他们必须进入任务管理器才能终止它或者等到所有1000个项目都被处理完毕。
我不会责怪你说,但当然!我应该使用CancellationTokenSource对象取消这些线程并调用Cancel ...但是从.net 4.0开始存在一些问题。首先,这仍然不会导致线程中止,这将提供中止异常,然后是线程终止,因此app域将需要等待线程正常完成,这意味着等待最后一次阻塞调用,这将是最后一次运行迭代(线程),最终调用po.CancellationToken.ThrowIfCancellationRequested
。在示例中,这意味着app域仍然可以保持活动长达2分钟,即使表单已关闭并取消调用。
注意,CancellationTokenSource上的Calling Cancel不会在处理线程上抛出异常,这确实会中断阻塞调用,类似于线程中止并停止执行。当所有其他线程(并发迭代)最终完成并返回时,一个异常被缓存,在启动线程(声明循环的地方)中抛出异常。
我选择不在CancellationTokenSource对象上使用Cancel选项。这是浪费的,并且可以说是违反了众所周知的控制异常代码流的反模式。
相反,实现一个简单的线程安全属性,即Bool stopExecuting,可以说是“更好”。然后在循环中检查stopExecuting的值,如果外部影响将值设置为true,我们可以采用备用路径优雅地关闭。由于我们不应该取消取消,这排除了检查CancellationTokenSource.IsCancellationRequested,否则这将是另一种选择。
如果条件在循环中适当,则类似以下内容;
if(loopState.ShouldExitCurrentIteration || loopState.IsExceptional || stopExecuting){loopState.Stop();返回;}
迭代现在将以“受控”的方式退出,并终止进一步的迭代,但正如我所说,这对我们必须等待长时间运行和阻塞每次迭代中的调用的问题几乎没有作用(并行循环线程),因为这些必须在每个线程可以选择检查它是否应该停止之前完成。
总之,当用户关闭表单时,将通过stopExecuting通知20个线程停止,但它们只会在完成执行长时间运行的函数调用后停止。
我们无法做任何关于应用程序域将始终保持活动并且仅在所有前台线程完成时释放的事实。这意味着等待在循环内完成的任何阻塞调用都会有延迟。
只有真正的线程中止才能中断阻塞调用,并且您必须尽可能地避免系统处于不稳定/未定义状态,而不必担心中止线程的异常处理程序。这是否适合程序员决定,根据他们选择维护的资源句柄以及在线程的最终块中关闭它们是多么容易。您可以使用令牌注册以取消作为半解决方案终止,即
CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
Parallel.ForEach(iListOfItems, po, (item, loopState) =>
{
using (cts.Token.Register(Thread.CurrentThread.Abort))
{
Try
{
Thread.Sleep(120000); // pretend web service call
}
Catch(ThreadAbortException ex)
{
// log etc.
}
Finally
{
// clean up here
}
}
});
但这仍然会在声明线程中导致异常。
考虑到所有事情,使用parallel.loop结构的中断阻塞调用可能是选项的一种方法,避免使用更复杂的库部分。但是为什么没有选择取消并避免在声明方法中抛出异常会让我觉得可能是疏忽。
但是我可以以同样的方式中止任务(在.Net 4.0中)而不是取消机制。我想立即杀死任务。
其他的回答者告诉你不要这样做。但是,是的,你可以做到。您可以提供Thread.Abort()
作为Task的取消机制调用的委托。以下是您可以配置的方法:
class HardAborter
{
public bool WasAborted { get; private set; }
private CancellationTokenSource Canceller { get; set; }
private Task<object> Worker { get; set; }
public void Start(Func<object> DoFunc)
{
WasAborted = false;
// start a task with a means to do a hard abort (unsafe!)
Canceller = new CancellationTokenSource();
Worker = Task.Factory.StartNew(() =>
{
try
{
// specify this thread's Abort() as the cancel delegate
using (Canceller.Token.Register(Thread.CurrentThread.Abort))
{
return DoFunc();
}
}
catch (ThreadAbortException)
{
WasAborted = true;
return false;
}
}, Canceller.Token);
}
public void Abort()
{
Canceller.Cancel();
}
}
免责声明:不要这样做。
以下是不应该做的事情的示例:
var doNotDoThis = new HardAborter();
// start a thread writing to the console
doNotDoThis.Start(() =>
{
while (true)
{
Thread.Sleep(100);
Console.Write(".");
}
return null;
});
// wait a second to see some output and show the WasAborted value as false
Thread.Sleep(1000);
Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted);
// wait another second, abort, and print the time
Thread.Sleep(1000);
doNotDoThis.Abort();
Console.WriteLine("Abort triggered at " + DateTime.Now);
// wait until the abort finishes and print the time
while (!doNotDoThis.WasAborted) { Thread.CurrentThread.Join(0); }
Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted + " at " + DateTime.Now);
Console.ReadKey();
虽然可以中止一个帖子,但实际上这样做几乎总是一个非常糟糕的主意。取消一个线程意味着该线程没有机会在其自身之后进行清理,使资源未被删除,以及处于未知状态的事物。
实际上,如果你中止一个线程,你应该只杀死一个进程。可悲的是,太多人认为ThreadAbort是一种可行的方法,可以阻止某些东西并继续下去,但事实并非如此。
由于任务作为线程运行,您可以在它们上调用ThreadAbort,但与通用线程一样,您几乎从不想这样做,除非作为最后的手段。
每个人都知道(希望)终止线程是不好的。问题是当你没有自己正在调用的代码时。如果这个代码在某些do / while无限循环中运行,它本身调用一些本机函数等,你基本上被卡住了。当你在你自己的代码终止,停止或处理呼叫时发生这种情况,开始射击坏人有点好(所以你不要自己成为坏人)。
因此,为了它的价值,我编写了这两个使用自己的本机线程的阻塞函数,而不是来自池中的线程或CLR创建的某个线程。如果发生超时,它们将停止线程:
// returns true if the call went to completion successfully, false otherwise
public static bool RunWithAbort(this Action action, int milliseconds) => RunWithAbort(action, new TimeSpan(0, 0, 0, 0, milliseconds));
public static bool RunWithAbort(this Action action, TimeSpan delay)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
var source = new CancellationTokenSource(delay);
var success = false;
var handle = IntPtr.Zero;
var fn = new Action(() =>
{
using (source.Token.Register(() => TerminateThread(handle, 0)))
{
action();
success = true;
}
});
handle = CreateThread(IntPtr.Zero, IntPtr.Zero, fn, IntPtr.Zero, 0, out var id);
WaitForSingleObject(handle, 100 + (int)delay.TotalMilliseconds);
CloseHandle(handle);
return success;
}
// returns what's the function should return if the call went to completion successfully, default(T) otherwise
public static T RunWithAbort<T>(this Func<T> func, int milliseconds) => RunWithAbort(func, new TimeSpan(0, 0, 0, 0, milliseconds));
public static T RunWithAbort<T>(this Func<T> func, TimeSpan delay)
{
if (func == null)
throw new ArgumentNullException(nameof(func));
var source = new CancellationTokenSource(delay);
var item = default(T);
var handle = IntPtr.Zero;
var fn = new Action(() =>
{
using (source.Token.Register(() => TerminateThread(handle, 0)))
{
item = func();
}
});
handle = CreateThread(IntPtr.Zero, IntPtr.Zero, fn, IntPtr.Zero, 0, out var id);
WaitForSingleObject(handle, 100 + (int)delay.TotalMilliseconds);
CloseHandle(handle);
return item;
}
[DllImport("kernel32")]
private以上是关于可以像中止一个Thread(Thread.Abort方法)一样中止一个Task吗?的主要内容,如果未能解决你的问题,请参考以下文章
std::thread,在线程中抛出异常会导致 Visual C++ 中的中止错误
std::thread 在 vc++ 的析构函数中获取中止异常
C# - 线程中止异常(Thread Abort Exception)重新抛出自身