在实现时间受限的方法时,我应该中止工作线程还是让它运行?

Fré*_*idi 4 c# multithreading wcf-lob-adapter

我目前正在为现有应用程序编写基于Web服务的前端.为此,我使用WCF LOB适配器SDK,它允许创建自定义WCF绑定,将外部数据和操作公开为Web服务.

SDK提供了一些实现的接口,并且它们的一些方法是时间约束的:实现期望在指定的时间跨度内完成其工作或抛出TimeoutException.

调查让我想到了" 实现C#通用超时 "的问题,明智地建议使用工作线程.有了这些知识,我可以写:

public MetadataRetrievalNode[] Browse(string nodeId, int childStartIndex,
    int maxChildNodes, TimeSpan timeout)
{
    Func<MetadataRetrievalNode[]> work = () => {
        // Return computed metadata...
    };

    IAsyncResult result = work.BeginInvoke(null, null);
    if (result.AsyncWaitHandle.WaitOne(timeout)) {
        return work.EndInvoke(result);
    } else {
        throw new TimeoutException();
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果工作线程超时,则不清楚如何处理工作线程.人们可以忘记它,就像上面的代码那样,或者可以中止它:

public MetadataRetrievalNode[] Browse(string nodeId, int childStartIndex,
    int maxChildNodes, TimeSpan timeout)
{
    Thread workerThread = null;
    Func<MetadataRetrievalNode[]> work = () => {
        workerThread = Thread.CurrentThread;
        // Return computed metadata...
    };

    IAsyncResult result = work.BeginInvoke(null, null);
    if (result.AsyncWaitHandle.WaitOne(timeout)) {
        return work.EndInvoke(result);
    } else {
        workerThread.Abort();
        throw new TimeoutException();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,中止一个线程被普遍认为是错误的.它打破了正在进行的工作,泄漏资源,使用锁定进行混乱,甚至不能保证线程实际上会停止运行.也就是说,HttpResponse.Redirect()每次调用时都会中止一个线程,而IIS似乎对此非常满意.也许它已经准备好以某种方式处理它.我的外部应用程序可能不是.

另一方面,如果我让工作线程继续运行,除了资源争用增加(池中可用线程较少)之外,无论如何都不会泄漏内存,因为work.EndInvoke()永远不会被调用?更具体地说,MetadataRetrievalNode[]返回的数组不会work永远存在吗?

这只是选择两个邪恶中较小的一个问题,还是有办法不中止工作线程并仍然回收使用的内存BeginInvoke()

Bri*_*eon 6

好吧,首先关闭Thread.Abort并不像它使用它那么糟糕.在2.0中对CLR进行了一些改进,修复了中止线程的几个主要问题.注意,这仍然很糟糕,所以避免它是最好的行动方案.如果你必须诉诸于中止线程,那么至少你应该考虑拆除中止源自的应用程序域.在大多数情况下,这将是非常具有侵入性的,并且无法解决可能的非托管资源损坏问题.

除此之外,在这种情况下中止还会产生其他影响.最重要的是你试图中止ThreadPool线程.我真的不确定最终结果是什么,它可能会有所不同,具体取决于框架的哪个版本正在发挥作用.

最好的做法是让您的Func<MetadataRetrievalNode[]>委托在安全点轮询变量,看它是否应该自行终止执行.

public MetadataRetrievalNode[] Browse(string nodeId, int childStartIndex, int maxChildNodes, TimeSpan timeout)
{
    bool terminate = false;

    Func<MetadataRetrievalNode[]> work = 
      () => 
      {
        // Do some work.

        Thread.MemoryBarrier(); // Ensure a fresh read of the terminate variable.
        if (terminate) throw new InvalidOperationException();

        // Do some work.

        Thread.MemoryBarrier(); // Ensure a fresh read of the terminate variable.
        if (terminate) throw new InvalidOperationException();

        // Return computed metadata...
      };

    IAsyncResult result = work.BeginInvoke(null, null);
    terminate = !result.AsyncWaitHandle.WaitOne(timeout);
    return work.EndInvoke(result); // This blocks until the delegate completes.
}
Run Code Online (Sandbox Code Playgroud)

棘手的部分是如何处理代理中的阻塞调用.显然,terminate如果委托正在阻塞调用中,则无法检查标志.但是,假设阻塞调用从罐头BCL等待mechansisms(之一发起的WaitHandle.WaitOne,Monitor.Wait等等),那么你可以使用Thread.Interrupt到"捅",并应立即解锁.

  • @Frédéric:这是一个非常复杂的问题.如果在COM调用中间中止,则可能会破坏COM对象的状态.由于这是非托管内存,拆除应用程序域将无效.我所知道的唯一可靠方法是在另一个进程中托管COM调用.您可以使用WCF或其他任何进行进程间通信.如果该方法执行时间过长,则可以简单地终止该过程.不幸的是,这是很多工作. (4认同)