#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);
}
}
}
Указанное выше изменение предназначено только для целей диагностики! После того, как вы найдете и устраните проблему, изменение может быть отменено.