Долгосрочный процесс приостановлен

У меня есть консольное приложение .NET 2.0, работающее на Windows Server GoDaddy VPS в среде Visual Studio 2010 IDE в режиме отладки (F5).

Приложение периодически зависает (как будто сборщик мусора временно приостановил выполнение), однако в редких случаях он никогда не возобновляет выполнение!

Я уже несколько месяцев дезориентирую это, и у меня заканчиваются идеи.

* Приложение работает так быстро, как может (он использует 100% использование ЦП), но при нормальном приоритете. Он также многопоточен.
* Когда приложение зависает, я могу разморозить его с помощью VS2010 IDE, приостановив/приостановив процесс (поскольку он работает в отладчике).
* Местоположение последнего выполнения, когда я приостанавливаю замороженный процесс, кажется несущественным.
* В то время как замораживание, использование ЦП по-прежнему на 100%.
* После размораживания он работает отлично до следующего замораживания.
* Сервер может запускать 70 дней между зависаниями, или это может сделать только 24 часа.
* Использование памяти остается относительно постоянным; нет никаких признаков утечки памяти.

У кого-нибудь есть подсказки для диагностики того, что именно происходит?

>  Он также многопоточен 
> 
> 

Это ключевая часть проблемы. Вы описываете очень типичный способ, которым многопоточная программа может плохо себя вести. Он страдает от тупика, одной из типичных проблем с резьбой.

Его можно сузить немного дальше от информации, очевидно, что ваш процесс не полностью заморожен, так как он все еще потребляет 100% -ный процессор. У вас, вероятно, есть горячий цикл ожидания в вашем коде, цикл, который вращается по другому потоку, сигнализируя о событии. Это, вероятно, вызовет особенно неприятное множество тупиков,  [**live-lock**](https://en.wikipedia.org/wiki/Live-lock#Livelock) . Живые блокировки очень чувствительны к синхронизации, незначительные изменения в порядке выполнения кода могут привести к его блокировке. И снова возвращайся.

Live-locks чрезвычайно сложно отлаживать, поскольку попытка сделать это заставляет условие исчезнуть. Подобно прикреплению отладчика или разрыву кода, достаточно изменить время потока и вывести его из состояния. Или добавление операторов регистрации в ваш код, общую стратегию для отладки проблем с потоками. Что изменяет время из-за издержек записи, что, в свою очередь, может привести к тому, что live-lock полностью исчезнет.

Неприятный материал и невозможно получить помощь с такой проблемой с сайта, такого как SO, поскольку он чрезвычайно зависит от кода. Для выяснения причины часто требуется тщательный анализ кода. И нередко резкое переписывание. Удачи вам.



Имеет ли приложение код «блокировки восстановления/предотвращения блокировки»? То есть, блокировка с тайм-аутом, а затем попытка снова, возможно, после сна?

Проверяет ли приложение коды ошибок (возвращаемые значения или исключения) и многократно повторяет попытку в случае ошибки?

Обратите внимание, что такой цикл может также произойти через цикл событий, где ваш код находится только в некотором обработчике событий. Он не должен быть фактическим циклом в вашем собственном коде. Хотя это, вероятно, не так, если приложение заморожено, указывая на заблокированный цикл событий.

Если у вас есть что-то вроде выше, вы можете попробовать  *смягчить*  проблему, сделав тайм-ауты и сны случайным интервалом, а также добавив короткие случайные спячки в случаях, когда ошибка может привести к смерти/динамический тупик. Если такой цикл чувствителен к производительности, добавьте счетчик и начните только сон со случайным, возможно, увеличением интервала после некоторого количества неудачных попыток. И убедитесь, что любой сон, который вы добавляете, не сплетается, когда что-то заблокировано.

Если ситуация будет происходить чаще, вы также можете использовать это, чтобы разделить ваш код и определить, какие циклы (потому что использование 100% CPU означает, что некоторые очень занятые циклы вращаются) несут ответственность. Но из-за редкости вопроса, я понимаю, вы будете счастливы, если проблема просто исчезнет на практике;)



Ну, три вещи здесь ...

Прежде всего, начните использовать GC сервера .NET:  [http://msdn. microsoft.com/en-us/library/ms229357.aspx](https://msdn.microsoft.com/en-us/library/ms229357.aspx) . Это, вероятно, не сделает ваше приложение незаблокированным.

Во-вторых, если вы можете сделать это на своей виртуальной машине: проверьте наличие обновлений. Это всегда кажется очевидным, но я видел многочисленные случаи, когда простое обновление 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);
    }

```

Обратите внимание на то, как вы закончите с небольшой утечкой памяти, и если вы удалите Thread.Sleep также с 100% -ным процессом процессора, даже если ваш основной поток все еще отвечает. Поскольку они разные потоки, отсюда очень легко заблокировать весь процесс - например, с помощью блокировки:

```
    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); } }
    }

```

Поэтому я вижу, что вы думаете: «Вы серьезно?»; дело в том, что вы можете делать что-то подобное, даже не осознавая этого. Вот где «выход» входит в картину:

IEnumerable от «yield» на самом деле IDisposable и как таковой реализует шаблон IDisposable. Объедините реализацию «yield» с блокировкой, забудьте вызвать IDisposable, перечислив ее «MoveNext» и т. Д., И вы получите довольно неприятное поведение, которое отражает вышеизложенное. Тем более, что финализаторы вызывают из очереди финализации отдельным потоком (!). Объедините его с бесконечным циклом или потоком небезопасного кода, и вы получите довольно неприятное неожиданное поведение, которое будет срабатывать в исключительных случаях (когда память закончится или когда GC-вещи он что-то сделает).

Другими словами: я бы проверял ваши расходные материалы и финализаторы и очень критично относился к ним. Убедитесь, что «yield» имеет неявные финализаторы и убедитесь, что вы вызываете IDisposable из того же потока. Некоторые примеры того, о чем вы должны быть осторожны:

```
    try
    {
        for (int i = 0; i < 10; ++i)
        {
            yield return "foo";
        }
    }
    finally
    {
       //Called by IDisposable
    }

```

а также

```
    lock (myLock)//'lock' а также'using' also trigger IDisposable
    {
        yield return "foo";
    }

```