我在从 Active Directory 获取的 PC 列表上运行并行操作。我用这个方法来检查电脑状态,比如电脑是否在线,或者特定目录是否存在。但是,由于这些有时很慢的操作的性质,我想包含一个超时,以便我的应用程序可以继续运行。
public static T MethodTimeout<T>(Func<T> f, int timeout, out bool completed)
{
T result = default(T);
var thread = new Thread(() => result = F());
thread.Start();
Completed = thread.Join(Timeout);
if (!Completed) thread.Abort();
return result;
}
这在大部分情况下都有效,但处理使用量似乎有点飙升,在少数情况下我遇到了内存不足异常。于是,我改用tasks的方式,希望ThreadPool
能解决上述问题:
public static T MethodTimeout<T>(Func<T> f, int timeout, out bool completed)
{
T result = default(T);
var timedTask = Task.Factory.StartNew(() => result = F());
Completed = timedTask.Wait(Timeout);
return result;
}
但是,我有一种感觉,我只是用挂起等待这些可能很长的任务完成的进程填满了 ThreadPool
。由于我将任务的函数作为参数传递,因此我看不到使用取消 token 的方法。但是,我在这些类(class)上的经验非常有限,我可能会遗漏一些高级技术。
下面是我使用上述方法的方式:
bool isReachable; // Check if the directory exists (1 second)
MethodTimeout(() => Directory.Exists(startDirectory), 1000, out isReachable);
请注意,我只是在通过 WMI 调用(也使用 MethodTimeout
)确认 PC 在线后才运行上述检查。通过我的早期测试,我知道在此之前检查目录效率低下。
我也愿意用更好的方法取代这种方法。
最佳答案
我可能是坏消息的先兆,但这种情况比大多数人想象的要难处理得多。看来您已经意识到了这一点。使用协作取消机制很好,但您必须能够真正取消耗时的操作才能有效地使用它们。问题是 Directory.Exists
无法取消。
第一个解决方案的问题是您要中止一个线程。这不是一个好主意,因为它会在不可预测的点停止线程。这可能会导致注入(inject)中止时在调用堆栈上执行的任何内容的数据结构损坏。在这种特殊情况下,如果 Thread.Abort
调用实际上挂起,我不会感到惊讶。原因是在非托管代码中执行时,中止通常会延迟。很可能 Directory.Exists
遵循非托管模块。如果是这种情况,则中止无论如何都不会起作用。
第二个解决方案的问题是您将任务孤立。 Directory.Exists
调用仍将在某处的线程池线程上执行。这是因为您实际上并没有取消它。
老实说,我真的不知道该怎么办。缺少可取消的 Directory.Exists
方法是非常有问题的。我的第一个想法是尝试从要测试的目录中写入或读取,作为检查其存在的代理。 FileStream
类确实有可取消的操作。事实上,许多方法甚至接受 CancellationToken
作为参数。或者您可以关闭 FileStream
,这也会取消所有挂起的操作。另一种选择可能是使用 Win32 API 函数来执行 IO。然后你可以打电话CancelSynchronousIo如有必要。
我知道这不是一个很好的答案,因为我真正做的只是告诉你什么不能做,但没有提供明确的解决方案。关键是最好的解决方案从可取消的操作开始。不幸的是,一些 BCL 类即使应该提供这些也没有提供。
关于c# - 在指定时间段后停止方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19718485/