16

老实说,我并不是打败一匹 。而且我已经阅读了关于线程终止的所有建议,但是,请考虑代码。它执行以下操作:

  1. 它启动一个线程(通过StartThread方法)
  2. 它调用数据库在 ServiceBroker 队列中查找任何内容。请注意WAITFOR命令 - 这意味着它将坐在那里,直到队列中有东西。这一切都在MonitorQueue方法中。
  3. 杀死线程。我试过.Interrupt了——它似乎什么也没做。然后我尝试.Abort了,它永远不应该被使用,但即使这样也没有任何作用。

    Thread thxMonitor = new Thread(MonitorQueue);
    void StartThread() {
        thxMonitor.Start();
    }
    
    void MonitorQueue(object obj) {
        var conn = new SqlConnection(connString);
        conn.Open();
        var cmd = conn.CreateCommand();
        cmd.CommandTimeout = 0; // forever and ever
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = "WAITFOR (RECEIVE CONVERT(int, message_body) AS Message FROM SBQ)";
    
        var dataTable = new DataTable();
        var da = new SqlDataAdapter(command);
    
        da.Fill(dataTable);
        da.Dispose();
    }
    
    void KillThreadByAnyMeansNecessary() {
        thxMonitor.Interrupt();
        thxMonitor.Abort();
    }
    

真的可以杀死一个线程吗?

4

5 回答 5

8

我讨厌不回答你的问题,但考虑以不同的方式解决这个问题。T-SQL 允许使用 WAITFOR 指定 TIMEOUT 参数,这样如果在一定时间内没有收到消息,则语句将退出并必须再次尝试。您在必须等待的模式中一遍又一遍地看到这一点。权衡是您不会在请求时立即使线程死亡——您必须等待超时到期,然后线程才会终止。

您希望这种情况发生得越快,超时间隔就越小。想要它立即发生吗?那么你应该改为轮询。

static bool _quit = false;

Thread thxMonitor = new Thread(MonitorQueue);
void StartThread() {
    thxMonitor.Start();
}

void MonitorQueue(object obj) {

    var conn = new SqlConnection(connString);
    conn.Open();
    var cmd = conn.CreateCommand();
    cmd.CommandType = CommandType.Text;
    cmd.CommandText = "WAITFOR (RECEIVE CONVERT(int, message_body) AS Message FROM SBQ) TIMEOUT 500";

    var dataTable = new DataTable();    

    while(!quit && !dataTable.AsEnumerable().Any()) {
        using (var da = new SqlDataAdapter(command)) {    
            da.Fill(dataTable);
        }
    }
}

void KillThreadByAnyMeansNecessary() {
    _quit = true;
}

编辑

尽管这感觉像是轮询队列,但实际上并非如此。当您进行轮询时,您正在积极检查某些内容,然后您正在等待避免“旋转”的情况,即您不断地烧毁 CPU(尽管有时您甚至不等待)。

考虑检查条目时轮询方案中发生的情况,然后等待 500 毫秒。如果队列中没有任何内容并且 200 毫秒后有消息到达,则在轮询时必须再等待 300 毫秒才能获取消息。对于超时,如果消息在“等待”方法的超时 200 毫秒后到达,则消息会立即得到处理。

轮询时的等待与紧密循环中轮询时的恒定高 CPU 所强制的时间延迟是轮询通常不能令人满意的原因。等待超时没有这样的缺点——唯一的权衡是你必须等待你的超时到期,然后你的线程才能死掉。

于 2012-09-18T00:37:09.313 回答
8

设置一个 Abort 标志来告诉线程需要终止。将虚拟记录附加到 ServiceBroker 队列。WAITFOR 然后返回。然后线程检查它的'Abort'标志,发现它被设置,从队列中删除虚拟记录并退出。

另一种变体是将“真正的”毒丸记录添加到由 ServiceBroker 监视的表的规范中 - 非法记录号等。这将完全避免以任何直接方式接触线程 - 总是一件好事:) 这可能会更复杂,特别是如果每​​个工作线程都希望在实际终止时通知,但如果工作线程仍然有效, ServiceBroker 和 DB 都在不同的盒子上。我将其添加为编辑,因为经过更多考虑,它似乎更灵活,毕竟,如果线程通常只通过它进行通信。数据库,为什么不只用数据库关闭它们呢?没有 Abort(),没有 Interrupt(),希望没有产生锁定的 Join()。

于 2012-09-18T01:06:21.607 回答
4

而不是杀死你的线程,改变你的代码使用 WAITFOR 一个小的超时。

超时后,检查线程是否被中断。

如果没有,请返回并再次等待。

是的,waitfor 的“全部意义”是等待某事。但是,如果您希望某些东西具有响应性,则不能要求一个线程等待 Infinity,然后期望它听其他任何东西。

于 2012-09-18T00:37:57.247 回答
4

不要这样做!严重地!

您需要调用来杀死线程的函数是TerminateThread函数,您可以通过 P/Invoke 调用该函数。关于为什么您不应该使用此方法的所有原因都在文档中

TerminateThread 是一个危险的函数,只能在最极端的情况下使用。仅当您确切知道目标线程在做什么时才应该调用 TerminateThread,并且您控制了目标线程在终止时可能正在运行的所有代码。例如,TerminateThread 可能会导致以下问题:

  • 如果目标线程拥有临界区,该临界区将不会被释放。
  • 如果目标线程正在从堆中分配内存,则不会释放堆锁。
  • 如果目标线程在终止时正在执行某些 kernel32 调用,则线程进程的 kernel32 状态可能不一致。
  • 如果目标线程正在操作共享 DLL 的全局状态,则 DLL 的状态可能会被破坏,从而影响 DLL 的其他用户。

需要注意的重要一点是粗体字,以及在 CLR / .Net 框架下,您永远不会确切知道目标线程在做什么(除非您碰巧编写了 CLR)。

澄清一下,在运行 .Net 代码的线程上调用 TerminateThread 很可能会使您的进程死锁,或者处于完全不可恢复的状态

如果您找不到某种方法来中止连接,那么您最好让该线程在后台运行,而不是尝试使用TerminateThread. 其他人已经发布了关于如何实现这一目标的替代建议。


Thread.Abort方法稍微安全一些,因为它会引发ThreadAbortException而不是立即拆除您的线程,但是这具有并不总是有效的缺点 - 如果 CLR 实际在该线程上运行代码,CLR 只能抛出异常,但是在这种情况下线程可能正在等待一些 IO 请求在本机 SQL Server 客户端代码中完成,这就是为什么您的调用Thread.Abort没有做任何事情,并且在控制权返回给 CLR 之前不会做任何事情。

Thread.Abort无论如何也有它自己的问题,通常被认为是一件坏事,但是它可能不会完全处理你的过程(尽管它仍然可能,这取决于运行的代码正在做什么)。

于 2012-09-18T13:39:38.343 回答
2

立即终止线程并不容易。有一个潜在的潜在问题与之相关:

你的线程获得了一个锁,然后你在它释放锁之前杀死它。现在需要锁的线程会卡住。

您可以使用一些全局变量来告诉线程停止。您必须在线程代码中手动检查该全局变量,如果看到它表明您应该停止,则返回。

请参考这个问题讨论同样的事情: 如何在 C# 中立即终止线程?

于 2012-09-18T00:20:25.543 回答