Метод Main (string[] args) не вызван

#c# #visual-studio #visual-studio-2017

#c# #visual-studio #visual-studio-2017

Вопрос:

Я использую Visual Studio 2017.

Всякий раз, когда я нажимаю F5 , чтобы начать отладку моей программы, я замечаю, что Main(string[] args) метод внутри Program класса не вызывается, хотя поля внутри Program инициализированы, как вы можете видеть на скриншоте ниже:

введите описание изображения здесь

После создания TcpClient экземпляра и последующего присвоения его соответствующему полю отладчик никогда не достигает точки останова, которую я установил для Main(string[] args) метода.

Просто чтобы быть уверенным, я установил объект запуска для моего проекта в качестве Program класса. Это не устранило проблему:

введите описание изображения здесь

Чего мне не хватает?

Редактировать:

Я добавил Console.WriteLine("Entering Main method...") внутри своего Main метода, но он не выводится на консоль, когда я начинаю отладку.

Буквально ничего (или, скорее, ничего сразу видимого) не происходит после создания TcpClient экземпляра — никаких исключений; программа не завершается самостоятельно; консоль остается пустой.

Редактировать:

Оказывается, внутри TcpClient конструктора происходит сбой.

Комментарии:

1. Может быть, это сбой раньше?

2. Так что же происходит ? Происходит ли немедленный сбой кода (например, при вызове этого конструктора)? Что произойдет, если вместо этого вы запустите его из командной строки?

3. Кроме того, попробуйте поместить Console.WriteLine("Hello World"); или что-то подобное в метод Main и посмотреть, печатает ли он

4. @KeithPaulBarrow нет — похоже, он работает, если он также является частным.

5. Вы абсолютно уверены, что конструктор TcpClient завершается? Если вы переместите назначение и вызов конструктора в Main (с выводом диагностики «до» и «после»), что произойдет? Действительно ли что-нибудь прослушивается на этом хосте / порту?

Ответ №1:

Помните, что TcpClient(string, int) конструктор открывает новое соединение в этот момент (документ):

Инициализирует новый экземпляр класса TcpClient и подключается к указанному порту на указанном хосте.

Этот конструктор создает новый TcpClient и выполняет попытку синхронного подключения к указанному имени хоста и номеру порта.

Если я скопирую / вставлю ваш код (вставляя свой собственный RemoteServerIpAddressString ), то я вижу, что приложение зависает, когда оно пытается создать TcpClient . Если я сломаю отладчик в этот момент, я увижу, что он застрял в System.Net.Sockets.Socket.DoConnect , который пытается подключиться к удаленному компьютеру.

Через некоторое время он сдается, генерирует исключение, и TypeInitializationException выдается, что приводит к сбою отладчика.

Это соответствует вашему наблюдению:

Буквально ничего (или, скорее, ничего не видно сразу) не происходит после создания экземпляра TcpClient — никаких исключений; программа не завершается самостоятельно; консоль остается пустой.

На данный момент TcpClient все еще пытается подключиться. Пока он не завершится успешно, тип никогда не инициализируется, и пока это не произойдет Main , он никогда не будет запущен. Если вы оставите его достаточно долго, он, вероятно, завершится неудачей, как и мой.

Если я удостоверюсь, что TcpClient подключается к порту, который открыт, то TcpClient конструктор сразу завершается и Main запускается.


Это очень плохая идея делать длительные вещи — особенно сетевые вещи — внутри статического конструктора. Среда CLR должна получать блокировку при инициализации типа, и это останавливает инициализацию других типов и может вызвать взаимоблокировки.

Вероятно, вы хотите либо создать TcpClient внутри своего Main метода, либо сконструировать его как:

 private static readonly TcpClient TcpClient = new TcpClient();
  

и затем в main:

 TcpClient.Connect(...);
  

Ответ №2:

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

Код, выделенный в вопросе, является инициализатором статического поля. Это будет выполняться при первом обращении к типу, перед любым статическим методом или даже статическим конструктором. Если инициализаторы или статический конструктор блокируют или выбрасывают, приложение завершается без вызова Main . Это означает, что для перехвата этих исключений нельзя использовать код обработки ошибок.

Этот гарантированный порядок очень упрощает реализацию простых одиночных элементов в C #. Двойная блокировка не требуется, поскольку порядок выполнения гарантирован. Проверьте статью Джона Скита о одноэлементной реализации :

 public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Singleton()    {    }
    private Singleton()   {    }

    public static Singleton Instance
    {
        get 
        {
            return instance;
        }
    }
}
  

Этого достаточно для создания потокобезопасного синглтона

Комментарии:

1. @canton7 упс. Это тоже есть в спецификации

Ответ №3:

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

Чтобы устранить проблему, я сделал это:

  • Переименовал мой Main метод в Main2 , чтобы он не конфликтовал со следующим изменением.
  • Создан другой программный класс, на этот раз чистый, который вызывает Main2 , как показано ниже:
 class CleanProgram {

    static void Main(string[] args) {
        try {
            Program.Main2(args);
        } catch (Exception ex) {
            Console.WriteLine("{0}", ex);
        }
    }

}
  

Указанное выше изменение предназначено только для целей диагностики! После того, как вы найдете и устраните проблему, изменение может быть отменено.