Нечастые зависания в многопоточном консольном приложении C# при использовании Console.Writeline() или Console.Write()

Я написал консольное приложение, которое использует console.write и console.writeline для ведения журналов. Приложение представляет собой серверное приложение, которое использует для связи асинхронные функции beginacceptconnection() и beginread() (Sockets). Иногда я получаю отчеты о том, что он зависает, и из ограниченной отладки, которую я могу сделать, я вижу, что проблема заключается в Console.Writeline() или Console.write().

Будучи многопоточным, я позаботился о блокировке класса ведения журнала, поэтому только один поток может регистрировать сообщение одновременно..... когда я поймал зависание, все, что я получаю, это потоки, блокирующие блокировку, и отчеты VS что управление перешло в Console.Write и ждет его возвращения... этого никогда не происходит.

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

// Logging Class

public class Logging
{
    // Lock to make the logging class thread safe.
    static readonly object _locker = new object();

    public delegate void msgHandlerWriteLineDelegate(string msg, Color col);
    public static event msgHandlerWriteLineDelegate themsgHandlerWriteLineDelegate;

    public delegate void msgHandlerWriteDelegate(string msg, Color col);
    public static event msgHandlerWriteDelegate themsgHandlerWriteDelegate;

    public static void Write(string a, Color Col)
    {
        if (themsgHandlerWriteDelegate != null)
        {
            lock (_locker)
            {
                themsgHandlerWriteDelegate(a, Col);
            }
        }
    }

    public static void Write(string a)
    {
        if (themsgHandlerWriteDelegate != null)
        {
            lock (_locker)
            {
                themsgHandlerWriteDelegate(a, Color.Black);
            }
        }
    }

    public static void WriteLine(string a, Color Col)
    {
        if (themsgHandlerWriteLineDelegate != null)
        {
            lock (_locker)
            {
                themsgHandlerWriteLineDelegate(a, Col);
            }
        }
    }

    public static void WriteLine(string a)
    {
        if (themsgHandlerWriteLineDelegate != null)
        {
            lock (_locker)
            {
                themsgHandlerWriteLineDelegate(a, Color.Black);
            }
        }
    }

    // Console Methods That implement the delegates in my logging class.

    public static void ConsoleWriteLine(string message, Color Col)
    {
        try
        {
            if (Col == Color.Black)
            {
                Console.ForegroundColor = ConsoleColor.Gray;
            }
            else
            {
                Console.ForegroundColor = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), Col.Name);
            }
            Thread.BeginCriticalRegion();
            Console.WriteLine(message);
            Thread.EndCriticalRegion();
            Console.ForegroundColor = ConsoleColor.Gray;
        }
        catch (ThreadAbortException ex)
        {
            Console.WriteLine("ThreadAbortException : " + ex.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception : " + ex.Message);
        }
    }

    public static void ConsoleWrite(string message, Color Col)
    {
        try
        {
            if (Col == Color.Black)
            {
                Console.ForegroundColor = ConsoleColor.Gray;
            }
            else
            {
                Console.ForegroundColor = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), Col.Name);
            }
            Thread.BeginCriticalRegion();
            Console.Write(message);//**THIS IS WHERE IS HANGS...IT NEVER RETURNS **
            Thread.EndCriticalRegion();
            Console.ForegroundColor = ConsoleColor.Gray;
        }
        catch (ThreadAbortException ex)
        {
            Console.WriteLine("ThreadAbortException : " + ex.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception : " + ex.Message);
        }
    }

    public static void ConsoleUpdate(string message)
    {
        try
        {
            Thread.BeginCriticalRegion();
            Console.WriteLine(message);//**THIS IS WHERE IS HANGS...IT NEVER RETURNS **
            Thread.EndCriticalRegion();
        }
        catch (ThreadAbortException ex)
        {
            Console.WriteLine("ThreadAbortException : " + ex.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception : " + ex.Message);
        }
    }

    // The main method...subscribes to delegates and spawns a thread to boot HW..main thread then exits.

    public static void Main()
    {
        Logging.themsgHandlerWriteDelegate += new Logging.msgHandlerWriteDelegate(ConsoleWrite);
        Logging.themsgHandlerWriteLineDelegate += new Logging.msgHandlerWriteLineDelegate(ConsoleWriteLine);
        Logging.themsgHandlerUpdateDelegate += new Logging.msgHandlerUpdateDelegate(ConsoleUpdate);
    }
}

public class ClassOnOtherThread
{
    // In a different class running on a different thread the following line occasionly invokes the error:

    private void BootHw(string Resource, string Resource2)
    {
        Logging.Write("\t\t[");
    }
}

Мое чтение MSDN предполагает, что Console.WriteLine и Console.Write являются потокобезопасными, и поэтому мне на самом деле не нужна блокировка вокруг него.... я также не могу поверить, что код Microsoft неверен (;-), и поэтому я предполагая, что это какое-то взаимодействие, которое выполняет мой код, создает ошибку.

Теперь мой вопрос: Должен ли я делать что-либо, чтобы предотвратить прерывание Console.WriteLine и Console.Write?... я предполагаю, что что-то прерывает его... но я действительно этого не знаю!!

Любая помощь была бы мне очень признательна.

С уважением,

Гордон.


person Gordon Roxburgh    schedule 27.06.2011    source источник
comment
Я опубликую некоторые завтра, как только я вернусь в офис.   -  person Gordon Roxburgh    schedule 28.06.2011
comment
Просто нажмите Ctrl+S, мгновенное зависание.   -  person Hans Passant    schedule 28.06.2011
comment
Увидев код: многое (действительно) можно удалить. Я не могу обнаружить очевидную ошибку, но Begin/EndCrtiticalRegion не предназначались и не нужны для Console.WriteLine(). Ваша ошибка, вероятно, в коде сокетов.   -  person Henk Holterman    schedule 30.06.2011
comment
Да, методы начала/конца критической секции действительно были моей попыткой исправить это, но это не сработало. Я все еще не понимаю, как код сокета может блокировать console.writeline()   -  person Gordon Roxburgh    schedule 01.07.2011
comment
Насколько вы уверены, что зависает WriteLine?   -  person Henk Holterman    schedule 01.07.2011
comment
Решил аналогичную проблему с помощью ReadLine после прочтения ответа Mostafa. Может быть связано с social.msdn.microsoft.com/Forums/vstudio/en-US/   -  person Jure Špik    schedule 28.04.2015
comment
Существует база знаний, которая должна это исправить, но WU не находит ее в моей версии 8.1: support.microsoft.com/en-us/kb/2805221 (выпуск CLR №4)   -  person Jure Špik    schedule 28.04.2015
comment
Вы упомянули, что используете сокеты. В моем случае это была библиотека WebSockets4Net, которая зависает System.IO.SyncTextWriter.WriteLine при использовании Console.WriteLine в обратных вызовах событий сокета. Предоставьте информацию о зависимостях проекта.   -  person Trinitron    schedule 12.08.2020


Ответы (4)


У меня такая же проблема.

Я использовал console.readkey() в основном потоке, чтобы предотвратить закрытие приложения в режиме отладки.

После того, как я заменил его бесконечным циклом, моя проблема была решена.

person mostafa bafandegan    schedule 11.09.2012
comment
Это сработало для меня ... измените ReadKey на ReadLine, и это будет исправлено. Спасибо - person Greg Ennis; 15.02.2013

Вы должны решить свою проблему, удалив блокировки вокруг журнала. Регистрация выполняется через Console.WriteLine, который синхронизирован (и потокобезопасен). Возможно, вы вызываете тупик через свой собственный механизм блокировки (хотя я не могу проверить, не видя кода).

person Ethan Cabiac    schedule 27.06.2011
comment
согласен, но это не объясняет, почему однажды вызванные методы console.writeline или console.write не возвращаются (таким образом создавая тупиковую ситуацию, о которой вы упоминаете).... я просто получаю зеленую стрелку в VS рядом со строкой с сообщением метод был вызван и не вернулся .... если я отменяю паузу и жду 30 секунд .... а затем снова делаю паузу, он находится в том же месте. С уважением, - person Gordon Roxburgh; 28.06.2011
comment
взаимоблокировка не создается отказом Console.WriteLine для возврата (это просто блокировка). Взаимная блокировка возникает, когда потоки борются за ресурсы. Поскольку вы не опубликовали никакого кода, я не могу сказать вам, в чем вы зашли в тупик. - person Ethan Cabiac; 28.06.2011
comment
Как тогда блокируется console.writeline()? Код опубликован. - person Gordon Roxburgh; 01.07.2011

Это немного сложно, но мне интересно, вызываете ли вы Console.WriteLine с объектами, чей метод ToString() блокируется. Если это так, вы можете попасть в тупиковую ситуацию в отношении блокировки, взятой внутри Console.WriteLine.

Однажды я разместил сообщение отчет об этой ошибке в Microsoft Connect, хотя, к сожалению, они отказались ее исправлять.

person Corey Kosak    schedule 27.06.2011
comment
Да, я только что прочитал ваш пост о «подключении»… кажется похожим… завтра я посмотрю на код, когда буду в офисе. - person Gordon Roxburgh; 28.06.2011

Я предполагаю, что ваше приложение запускается другим процессом, который перенаправляет stderr и stdout. Если ваш процесс-наблюдатель использует ReadToEnd() для обоих потоков в одном потоке вы можете заблокировать.

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

Если вы играли со своим классом журнала, я подозреваю, что вы изменили базовый поток Console.Out на свой собственный. Пожалуйста, выложите хотя бы стек вызовов, на котором висит ваше приложение, чтобы нам было что анализировать. Есть много способов выстрелить себе в ногу, если вы замените поток консоли на свой собственный.

Ваш, Алоис Краус

person Alois Kraus    schedule 27.06.2011
comment
У меня есть фото, но мне не разрешили его опубликовать, так как это мой первый пост на этом сайте. - person Gordon Roxburgh; 28.06.2011
comment
У меня есть фото, но мне не разрешили его опубликовать, так как это мой первый пост на этом сайте. Он показывает строку, которую он висит на активных потоках и стеке вызовов. Я мог бы отправить его, если вы думаете, что это поможет? Моя программа представляет собой .exe, который запускается и порождает два потока для загрузки двух идентичных наборов HW. Однако ошибка, которую я описываю, наблюдается, когда загружается только один поток (и один набор HW). Затем основной поток завершается (хотя я сделал t1.join, но ошибка все еще видна. - person Gordon Roxburgh; 28.06.2011
comment
В этом новом потоке, когда я загружаю HW, я вызываю свой класс ведения журнала для отображения сообщений о загрузке. Эти статические методы ведения журнала вызывают делегата, который, если подписчик реализует его, будет отображать сообщение.... в данный момент я просто пишу консольное приложение, но в какой-то момент могу изменить его на форму окна.... в любом случае Logging. Метод WriteLine(), который я реализовал, подписан методом, который реализует его с помощью Console.Writeline()... иногда, когда он зависает, и я нажимаю паузу в VS, все, что я вижу, застрял ли 1-й порожденный поток внутри Console.Writeline() метод - person Gordon Roxburgh; 28.06.2011
comment
он никогда не возвращается ...... Я быстро прочитал материал перенаправления консоли, о котором вы упомянули..... зачем мне это нужно делать? Это потому, что основной поток не тот, который пишет в консоль. С уважением, - person Gordon Roxburgh; 28.06.2011
comment
Да, скриншот висящей темы не помешал бы. Отправьте его akraus1 по адресу gmx dot de. Ваше консольное приложение запущено другим процессом, который читает/перенаправляет ваш вывод? - person Alois Kraus; 30.06.2011