8

我正在从 Active Directory 获取的 PC 列表上运行并行操作。我正在使用这种方法检查 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;
}

这在大多数情况下都有效,但处理使用率似乎有点飙升,在少数情况下,我遇到了内存不足的异常。所以,我后来改变了使用任务的方法,希望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等待这些可能很长的任务完成的进程。由于我将任务的函数作为参数传递,我看不到使用取消令牌的方法。然而,我对这些课程的经验非常有限,我可能会错过一些优秀的技术。

这是我一直在使用上述方法的方式:

bool isReachable; // Check if the directory exists (1 second)
MethodTimeout(() => Directory.Exists(startDirectory), 1000, out isReachable);

快速说明,我只是在通过 WMI 调用(也使用MethodTimeout)确认 PC 在线后才运行上述检查。通过我的早期测试,我知道在此之前检查目录最终效率低下。

我也愿意用更好的方法代替这种方法。

4

3 回答 3

6

我可能是这里坏消息的预兆,但这种情况比大多数人想象的更难处理。看来你已经开始接受了。使用协作取消机制固然很好,但是您必须能够实际取消耗时的操作才能有效地使用它们。问题是Directory.Exists不能取消。

您的第一个解决方案的问题是您正在中止一个线程。这不是一个好主意,因为它会在不可预测的点停止线程。这可能会导致在注入中止时在调用堆栈上执行的任何内容的数据结构损坏。Thread.Abort在这种特殊情况下,如果电话真的挂了,我不会感到惊讶。原因是在非托管代码中执行时,通常会延迟中止。它很可能Directory.Exists遵循非托管模块。如果是这种情况,那么中止无论如何都不会起作用。

第二种解决方案的问题是您将任务孤立。该Directory.Exists调用仍将在某处的线程池线程上执行。这是因为您实际上并没有取消它。

老实说,我不确定该怎么做。缺乏可取消的Directory.Exists方法是很成问题的。我的第一个想法是尝试从要测试的目录中写入或读取,作为检查其存在的代理。该类FileStream确实具有可取消的操作。事实上,许多方法甚至接受 aCancellationToken作为参数。或者您可以关闭FileStreamand 也将取消任何待处理的操作。另一种选择可能是使用 Win32 API 函数来执行 IO。然后,如有必要,您可以调用CancelSynchronousIo 。

我知道这不是一个答案,因为我真正所做的只是告诉您您不能做什么,但没有提供明确的解决方案。关键是最好的解决方案是从可取消的操作开始的。不幸的是,一些 BCL 类即使在它们应该提供的时候也没有提供这些。

于 2013-11-01T02:50:14.693 回答
4

如果您使用的是 .Net 4.5,您可以使用CancelAfter

var cts = new CancellationTokenSource(3000); // Set timeout

Task.Run(() =>
{
    while (!cts.Token.IsCancellationRequested)
    {
        // Doing Work...
    }

}, cts.Token);
于 2013-10-31T23:50:55.110 回答
0

像这样的东西可以吗?您必须要求消费者将 a 传递给您,该 aFunc需要 a CancellationTokenSource。那Func将负责检查IsCancellationRequested其代码中的适当位置...

public static T MethodTimeout<T>(Func<T, CancellationTokenSource> F, 
                                 int Timeout, out bool Completed)
{
    T result = default(T);
    var source = new CancellationTokenSource(Timeout);
    var timedTask = Task.Factory.StartNew(() => result = F(source));
    Completed = timedTask.Wait(Timeout);
    if(!Completed) source.Cancel();
    return result;
}

我对 TPL 的了解很少,如果我的代码不太正确,请见谅。没有办法用我的旧版本 VS 测试它。欢迎更正/编辑:)

于 2013-11-01T00:11:12.523 回答