【问题标题】:Thread.Join(int) not killing thread after specified timeout in C#Thread.Join(int) 在 C# 中指定超时后不杀死线程
【发布时间】:2012-04-05 16:52:48
【问题描述】:

在我的 Windows 窗体应用程序中,我正在尝试测试用户访问远程计算机共享文件夹的能力。我这样做的方式(我确信有更好的方法......但我不知道它们)是检查远程机器上是否存在特定目录(我正在这样做由于我在组织中遇到的防火墙/其他安全限制)。如果用户有权访问共享文件夹,那么它会立即返回,但如果他们没有,它就会永远挂起。为了解决这个问题,我将检查放入另一个线程并仅等待 1000 毫秒,然后确定用户无法点击该共享。但是,当我这样做时,它仍然挂起,就好像它从未在同一个线程中运行过一样。

是什么导致它挂起,我该如何解决?我认为它在一个单独的线程中的事实将允许我让线程在后台自行完成.

这是我的代码:

bool canHitPath = false;
Thread thread = new Thread(new ThreadStart(() =>
{
    canHitPath = Directory.Exists(compInfo.Path);
}));
thread.Start();
thread.Join(1000);

if (canHitPath == false)
{
    throw new Exception("Cannot hit folder: " + compInfo.Path);
}

编辑:我觉得应该补充一下抛出异常的那一行IS HIT。我已经对此进行了调试并对其进行了验证……但是,当引发异常时,就是我的程序挂起的时候。 (我还可以补充一点,异常是在调用方法中捕获的,而我从来没有到达调试器中的 catch 语句。)

【问题讨论】:

标签: c# multithreading winforms


【解决方案1】:

您的 cmets 清楚地表明异常实际上是被抛出和捕获的。所以代码执行至少超过了这段代码,我们无法从 sn-p 看出它在做什么。

您确实犯了一个错误,您忘记将线程的 IsBackground 属性设置为 true。没有它,程序就无法终止。这是您可能得出结论“它正在阻塞!”的一种方式。如果这个猜测不准确,那么我们需要查看主线程的调用堆栈来了解它在做什么。最好通过打开非托管调试支持并启用 Microsoft 符号服务器来捕获,这样我们就可以看到整个调用堆栈,而不仅仅是它的托管部分。

一种完全不同的方法是使用 Ping 类来探测服务器。

【讨论】:

  • 我刚刚尝试了 IsBackground 的事情。我得到了相同的结果......并且也澄清了:我永远不会捕获我的异常被抛出,我不希望我的程序在抛出异常时终止。此外,我实际上在上面列出的代码之前执行了 Ping。但是,如果计算机关闭,ping 仍然有效……我已经测试过了。但是对目录存在的测试不仅告诉我权限是否正确......而且还告诉我远程机器是否打开......当然,如果目录确实存在,我也需要知道。
  • 我需要查看堆栈跟踪以进一步改进我的猜测。水晶球今天度过了艰难的一天。
【解决方案2】:

我猜这是因为当你打电话时:

canHitInstallPath = Directory.Exists(compInfo.InstallPath);

Exists 方法保存了tread 的执行流程(它是一个不间断的调用)。如果它挂起 30 秒,您的线程将等待 30 秒,直到它有机会检查 Thread.Join(1000) 是否已经过去。

请注意,Thread.Join() 方法只会阻塞调用线程(通常是应用程序的主执行线程),直到您的线程对象完成。在等待特定线程完成执行时,您仍然可以让其他线程在后台执行。

发件人:

Thread.Join Method (System.Threading)

要考虑的另一件事:仅检查文件夹是否存在并不能说明用户是否可以读取或将文件写入文件夹。您最好的选择是尝试写入或读取文件夹的文件。这样您可以确保用户在该文件夹中具有权限。

编辑

在您的情况下,只有在等待线程完成时您可以做其他事情时,线程才有意义。如果你不能,他们的线程根本没有帮助你。

EDIT2

支持我的回答的链接:File Descriptors And Multithreaded Programs

EDIT3

你最好的选择是创建一个杀手线程。当该线程挂起超过 X 秒时,该线程将终止 DirectoryExists 线程。

【讨论】:

  • 仅供参考,布兰登在问题中指出Directory.Exists() 需要很长时间,因为它是一条远程路径。如果网络连接丢失,操作系统可能需要 30 秒(甚至更长,IIRC)才能中止 IO 操作。 (即使在 Windows 资源管理器中,您也可以重现此挂起。)他试图做的是将 IO 等待隔离到另一个线程,以便更快地检测到故障。
  • 如何执行 EDIT3 中所述的内容?我最初尝试做一个睡眠而不是一个连接,然后在线程上做一个中止......但我收到了相同的结果。
【解决方案3】:

我发现了真正的问题:

属性 compInfo.Path 正在检查远程文件系统上是否存在目录以确定远程计算机是否为 64 位。根据结果​​,它返回不同的值。我尝试注释掉检查并成功执行。这解释了为什么我无法通过异常的抛出,我在异常的消息中调用 compInfo.Path。

但是,我认为我们从“真正的问题”中学到了很多:

  1. 我在问题中发布的代码(原样)运行良好。
  2. thread.Join(int) 将在指定的时间间隔后退出,无论线程可能仍在执行代码。
  3. 正在加入的线程可以运行 IO 操作(从而占用文件/目录),并且在执行 thread.Join(int) 时仍会出现所需的结果。
  4. 使用调试器上的“进入”按钮会显示很多信息……甚至是关于您自己的“可靠”代码。 :)

感谢大家的帮助、耐心和深思熟虑的意见/见解。

【讨论】:

    【解决方案4】:

    也许看看Task Parallel Library。 TPL 旨在最大限度地提高性能,并且可以处理任何问题。虽然这也可能是完全矫枉过正的情况。

    【讨论】:

    • 我只是尝试使用 Task 类代替 Thread 类来实现它。我得到完全相同的结果。不过感谢您的评论,我完全忘记了使用任务而不是线程。
    • @Brandon 你使用什么库并不重要。这总会发生。
    【解决方案5】:

    Thread.Join 是一个阻塞调用,它会阻塞 调用它的线程,直到 on 调用它的线程退出。

    你基本上是在启动一个新线程来做后台工作,然后告诉主线程等到它完成。实际上是同步进行的。

    【讨论】:

    • 是的,我一开始也是这么想的。但他正在使用需要超时的Thread.Join() 的重载,所以至少这种行为是不直观的。
    • Thread.Join(1000) 无论如何都会在一秒钟后返回。不管它在做什么,它都会在一秒钟后杀死线程。它不应该阻塞更多——我刚刚复制粘贴了这段代码并将ExistsThread.Sleep(30000) 交换,它只阻塞了一秒钟。不知道OP的情况有什么问题...
    • @J...:说真的,超时后它会杀死线程吗?我在文档中没有看到任何参考。
    • @J...:另外,Sleepinterruptible 等待,而 OP 遇到的问题是 uninterruptible IO等待。它们根本不是一回事。
    • 嗯,这绝对不是“杀死”我的线程。正如我的问题中所解释的那样,我确实故意将超时放在那里。
    猜你喜欢
    • 2022-10-16
    • 2016-07-10
    • 2015-10-30
    • 2012-11-18
    • 1970-01-01
    • 2011-02-13
    • 2020-02-22
    • 2012-05-30
    • 1970-01-01
    相关资源
    最近更新 更多