【问题标题】:Stopping a method after a specified time period在指定时间段后停止方法
【发布时间】:2013-10-31 23:39:27
【问题描述】:

我正在从 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 在线后才运行上述检查。通过我的早期测试,我知道在此之前检查目录最终效率低下。

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

【问题讨论】:

  • 顺便说一句,.Net 命名约定说方法的参数应该在lowerCase 中命名。
  • 另外,如果你不理解这个问题,盲目地改变你调用代码的方式很可能不会有任何帮助。您需要找出这些问题的原因。
  • this 有帮助吗?它采用将任务包装在另一个任务中的方法。你能强迫用户给你一个Func,它需要一个CancellationTokenSource吗?
  • 不确定我是否同意“盲目地”,但我会承认隧道视野。我知道尝试管理我在使用 Thread.Start 时处理的两个异常可以通过实现线程池来缓解。我可能需要缩小视野,看看更大的图景,我最终会这样做。

标签: c# multithreading c#-4.0 active-directory task-parallel-library


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 我实际上认为这是一个很好的答案,感谢您解释 Directory 类。我今天就测试这个方向,看看能不能利用可以取消的方法。
【解决方案2】:

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

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

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

}, cts.Token);

【讨论】:

  • 不幸的是,我仅限于 .NET 4.0。知道仍然有用,将其记录下来。
  • 你能告诉我,如果我有方法 DoWork(),我怎样才能在 3 秒后停止它?我不能只在while循环中写这个
【解决方案3】:

这样的东西可以吗?您必须要求消费者将 Func 传递给您,该 Func 需要 CancellationTokenSourceFunc 将负责在其代码中的适当位置检查 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 来测试它。欢迎更正/编辑:)

【讨论】:

  • 我可以将它用于我可以直接控制的方法,但不能用于 Directory.Exists 之类的方法。听起来我无论如何都需要放弃那条路线,所以这可能很有用。谢谢。
  • 哇不知道Directory.Exists 可能需要很长时间,您可能想取消它。我不确定您所说的“我的忠诚度在于每秒的操作数”是什么意思,但是如果您想要一种方法来限制同时运行的 事物 的数量,请查看RateLimit(非BCL,只是一个小类)。
  • 我昨天在大约 5500 台计算机上运行了这个程序,到最后,我在用于枚举远程主机上的目录的方法上遇到了严重的问题。我发现添加 Directory.Exists 显着加快了挂起电话的速度。奇怪的是,我已经对这些计算机进行了 WMI 检查并确定它们在线,但这些调用可能是由于 PC 在检查和枚举目录之间的时间内关闭。但是我的修复并不是真正的修复,因为它只是抵消了线程池的延迟,这同样糟糕。
猜你喜欢
  • 2012-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 1970-01-01
  • 2021-12-27
相关资源
最近更新 更多