我设计并制作了一个用于高性能,多线程邮件合并的原型应用程序,以作为Windows服务(C#)运行。这个问题涉及问题的一个棘手部分,如果进程挂起数据库调用该怎么办。我研究了很多。我已经阅读了很多关于线程取消的文章,我最终只看到了一种方法,即thread.Abort()。是的,我知道,绝对不要使用Thread.Abort(),所以我一直在研究如何以另一种方式做到这一点,正如我所看到的,没有其他选择。我会告诉你为什么,希望你能告诉我为什么我错了。
仅供参考,这些是长时间运行的线程,因此无论如何TPL都会将它们放在ThreadPool之外。
TPL只是一个很好的Thread包装器,所以我完全看不到一个任务可以做什么线程不能。它的完成方式不同。
使用线程,您有两种选择来停止它。 1.在处理循环中进行线程轮询以查看标志是否已请求取消并且只是结束处理并让线程死亡。没问题。 2.调用Thread.Abort()(然后捕获异常,进行加入并担心最终等)。
这是线程中的数据库调用,因此一旦启动轮询就无法工作。
另一方面,如果你使用TPL和CancellationToken,在我看来你仍然在轮询然后创建一个例外。它看起来像我在案例1中描述的那样与线程相同。一旦我启动该数据库调用(我还打算在其周围放置async / await),我无法测试CancellationToken中的更改。就此而言,TPL更糟糕,因为在Db读取期间调用CancellationToken将完全不做任何事情,远远少于Thread.Abort()会做的事情。
我无法相信这是一个独特的问题,但我还没有找到真正的解决方案,而且我已经阅读了很多内容。无论是线程还是任务,工作线程必须轮询才能知道它应该停止然后停止(当连接到Db时不可能。它不在循环中。)或者线程必须中止,抛出一个ThreadAbortedException或TaskCanceledException。
我目前的计划是将每项工作作为一个长期的线程开始。如果线程超过了时间限制,我将调用Thread.Abort,在线程中捕获异常,然后在Abort()之后对线程执行Join()。
我对建议非常非常开放......谢谢,迈克
我会把这个链接,因为它声称这样做,但我很难搞清楚,并且没有回复让我认为它会起作用 multi-threading-cross-class-cancellation-with-tpl
哦,这看起来很有可能,但我不知道Treating a Thread as a Service
答案 0 :(得分:3)
您无法实际取消数据库操作。请求通过网络发送;它"那里"现在,没有拉回来。您真正做的最好的事情就是忽略返回的响应,并继续执行您在操作实际完成时执行的任何代码。认识到这是什么很重要;这实际上并没有取消任何东西,即使你没有完成它也只是继续前进。这是一个非常重要的区别。
如果您有一些任务,并且希望它在您希望的时候被取消,您可以创建一个使用CancellationToken
的延续,以便在令牌指示时将延续标记为已取消它应该是,或者在任务完成时完成。然后,您可以使用该延续Task
代替所有延续的实际基础任务,如果令牌被取消,任务将被取消。
public static Task WithCancellation(this Task task
, CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
}
public static Task<T> WithCancellation<T>(this Task<T> task
, CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
}
然后,您可以执行给定任务,传入取消令牌,并返回具有相同结果的任务,除非更改了取消语义。
答案 1 :(得分:1)
您的线程取消还有其他几个选项。例如,您的线程可以进行异步数据库调用,然后等待它和取消令牌。例如:
// cmd is a SqlCommand object
// token is a cancellation token
IAsyncResult ia = cmd.BeginExecuteNonQuery(); // starts an async request
WaitHandle[] handles = new WaitHandle[]{token.WaitHandle, ia.AsyncWaitHandle};
var ix = WaitHandle.WaitAny(handles);
if (ix == 0)
{
// cancellation was requested
}
else if (ix == 1)
{
// async database operation is done. Harvest the result.
}
如果操作被取消,则无需抛出异常。并且不需要Thread.Abort
。
Task
这一切都变得更加清晰,但它基本上是相同的。 Task
处理常见错误并帮助您更好地完成所有部分的整合。
你说:
TPL只是一个很好的Thread包装器,所以我完全看不到一个任务可以做什么线程不能。它的完成方式不同。
这是真的,就目前而言。毕竟,C#只是汇编语言程序的一个很好的包装器,所以我看不出C#程序在汇编语言中无法做到的事情。但使用C#可以更轻松,更快速地完成它。
同样适用于TPL或任务之间的区别,以及管理自己的线程。你可以做各种管理你自己的线程的东西,或者你可以让TPL处理所有的细节,更有可能做到正确。