C# 长时间运行的进程已暂停

C# 长时间运行的进程已暂停,c#,.net,windows,multithreading,garbage-collection,C#,.net,Windows,Multithreading,Garbage Collection,我有一个.NET 2.0控制台应用程序,在Visual Studio 2010 IDE的Windows服务器GoDaddy VPS上以调试模式(F5)运行 应用程序会定期冻结(就像垃圾收集器暂时暂停执行一样),但在极少数情况下,它不会恢复执行 我已经诊断这个好几个月了,现在已经没有主意了 应用程序以尽可能快的速度运行(它使用100%的CPU使用率),但优先级正常。它也是多线程的 当应用程序冻结时,我可以使用VS2010 IDE通过暂停/取消暂停进程来解冻它(因为它在调试器中运行) 当我暂停冻结

我有一个.NET 2.0控制台应用程序,在Visual Studio 2010 IDE的Windows服务器GoDaddy VPS上以调试模式(F5)运行

应用程序会定期冻结(就像垃圾收集器暂时暂停执行一样),但在极少数情况下,它不会恢复执行

我已经诊断这个好几个月了,现在已经没有主意了

  • 应用程序以尽可能快的速度运行(它使用100%的CPU使用率),但优先级正常。它也是多线程的
  • 当应用程序冻结时,我可以使用VS2010 IDE通过暂停/取消暂停进程来解冻它(因为它在调试器中运行)
  • 当我暂停冻结的进程时,最后一次执行的位置似乎无关紧要
  • 虽然冻结,CPU使用率仍然是100%
  • 解冻后,它运行良好,直到下一次冻结
  • 服务器可能在两次冻结之间运行70天,也可能只运行24小时
  • 内存使用保持相对稳定;没有任何内存泄漏的迹象
有人有什么诊断到底发生了什么的窍门吗

它也是多线程的

这是问题的关键部分。您描述的是一种多线程程序可能出现错误行为的典型方式。它遇到了死锁,这是线程的典型问题之一

它可以从信息中进一步缩小,显然您的进程并没有完全冻结,因为它仍然消耗100%的cpu。您的代码中可能有一个热等待循环,一个在另一个线程上旋转并发出事件信号的循环。这可能会导致一种特别恶劣的僵局,一个。活动锁对时间非常敏感,对代码运行顺序的微小更改可能会使它撞上活动锁。然后再回来

调试活动锁非常困难,因为尝试这样做会使条件消失。就像附加调试器或中断代码一样,足以改变线程计时并使其脱离状态。或者在代码中添加日志语句,这是调试线程问题的常用策略。这会由于日志开销而改变计时,而日志开销又会使活动锁完全消失

讨厌的东西,不可能从这样的网站上获得帮助,因为它非常依赖于代码。通常需要对代码进行彻底审查才能找到原因。而且经常是激烈的重写。祝您好运。

应用程序是否有“死锁恢复/预防”代码?也就是说,用timout锁定,然后再试一次,也许是在睡觉后

应用程序是否检查错误代码(返回值或异常),并在任何地方出现错误时重复重试

请注意,这样的循环也可以通过事件循环发生,其中您的代码仅在某些事件处理程序中。它不必是您自己代码中的实际循环。虽然情况可能并非如此,但如果应用程序被冻结,则表明事件循环被阻止

如果您有类似于上面的内容,您可以尝试通过将超时和睡眠设置为随机间隔,以及在错误可能产生死锁/活锁的情况下添加短期随机睡眠来缓解问题。如果这样的循环对性能敏感,请添加一个计数器,并仅开始随机休眠,可能在多次失败重试后增加间隔。并确保您添加的任何睡眠都不会在锁定某些内容时睡眠


如果这种情况发生得更频繁,您还可以使用它将代码一分为二,并确定哪些循环(因为100%的CPU使用率意味着,一些非常繁忙的循环正在旋转)负责。但从这一罕见的问题来看,我想如果这个问题在实践中消失了,你会很高兴的;)

这里有三件事

首先,开始使用.NET的服务器GC:。这可能会使您的应用程序不被阻止

第二,如果你能在你的虚拟机上做到这一点:检查更新。这似乎总是显而易见的,但我见过许多情况下,一个简单的windows更新修复奇怪的问题

第三,我想提出一个关于对象生命周期的观点,这可能是这里的问题之一。这是一个很长的故事发生了什么,所以请容忍我

对象的生命周期基本上是构造-垃圾收集-终结。所有三个进程都在一个单独的线程中运行。GC将数据传递给终结线程,该线程具有一个调用“析构函数”的队列

因此,如果您有一个执行奇怪操作的终结器,可以说:

public class FinalizerObject
{
    public FinalizerObject(int n)
    {
        Console.WriteLine("Constructed {0}", n);
        this.n = n;
    }

    private int n;

    ~FinalizerObject()
    {
        while (true) { Console.WriteLine("Finalizing {0}...", n); System.Threading.Thread.Sleep(1000); }
    }
}
由于终结器在处理队列的单独线程中运行,因此对于您的应用程序来说,单个终结器执行一些愚蠢的操作是一个严重的问题。通过使用上述类2次,您可以看到这一点:

    static void Main(string[] args)
    {
        SomeMethod();
        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();
        Console.WriteLine("All done.");
        Console.ReadLine();
    }

    static void SomeMethod()
    {
        var obj2 = new FinalizerObject(1);
        var obj3 = new FinalizerObject(2);
    }
请注意,如果您删除了线程,您最终会出现一个小的内存泄漏。使用100%的CPU进程进行睡眠-即使主线程仍在响应。因为它们是不同的线程,所以从现在开始很容易阻止整个进程-例如,通过使用锁:

    static void Main(string[] args)
    {
        SomeMethod();
        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();
        Thread.Sleep(1000);
        lock (lockObject)
        {
            Console.WriteLine("All done.");
        }
        Console.ReadLine();
    }

    static object lockObject = new Program();

    static void SomeMethod()
    {
        var obj2 = new FinalizerObject(1, lockObject);
        var obj3 = new FinalizerObject(2, lockObject);
    }

    [...]

    ~FinalizerObject()
    {
        lock (lockObject) { while (true) { Console.WriteLine("Finalizing {0}...", n); System.Threading.Thread.Sleep(1000); } }
    }
所以我可以看到你在想‘你是认真的吗?’;事实上,你可能正在做类似的事情,甚至没有意识到这一点。这就是“收益率”出现的原因:

“yield”中的IEnumerable实际上是IDisposable的,因此实现IDisposable模式。将您的“yield”实现与锁结合起来,忘记调用IDisposable,而是使用“MoveNext”等来枚举它。您会得到一些反映上述情况的非常恶劣的行为。特别是因为终结器是由单独的线程(!)从终结队列中调用的。将它与无休止的循环或线程不安全的代码结合起来,您将得到一些非常令人讨厌的意外行为,这些行为将在异常情况下触发(当内存耗尽时,或者当GC执行它应该执行的操作时)

换句话说,我会
    try
    {
        for (int i = 0; i < 10; ++i)
        {
            yield return "foo";
        }
    }
    finally
    {
        // Called by IDisposable
    }
    lock (myLock) // 'lock' and 'using' also trigger IDisposable
    {
        yield return "foo";
    }