3 回答

TA贡献1900条经验 获得超5个赞
请记住,TcpClient(string, int)
构造函数会在此时打开一个新连接 ( doc ):
初始化 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();
然后主要是:
TcpClient.Connect(...);

TA贡献1946条经验 获得超3个赞
本例中的静态字段初始值设定项(Program 类)不应包含可能抛出或超时的代码。
问题中突出显示的代码是静态字段初始值设定项。这将在第一次访问类型时运行,在任何静态方法甚至静态构造函数之前运行。如果初始值设定项或静态构造函数阻塞或抛出,应用程序将终止而不调用Main
. 这意味着没有错误处理代码可用于捕获这些异常。
这种有保证的顺序使得在 C# 中实现简单的单例变得非常容易。由于执行顺序得到保证,因此不需要双重锁定。查看 Jon Skeet 关于Singleton implementation的文章:
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;
}
}
}
这足以创建一个线程安全的单例

TA贡献1818条经验 获得超7个赞
我正在添加一个新的答案,因为我在隐藏类加载和其他初始化异常的更复杂的托管环境中遇到了类似的问题。
为了解决这个问题,我这样做了:
将我的Main方法重命名为Main2,因此它不会与下一个更改冲突。
创建了另一个 Program 类,这次是干净的,它调用Main2,如下所示:
class CleanProgram {
static void Main(string[] args) {
try {
Program.Main2(args);
} catch (Exception ex) {
Console.WriteLine("{0}", ex);
}
}
}
上面的更改仅用于诊断目的!找到并解决问题后,可以撤消更改。
- 3 回答
- 0 关注
- 153 浏览
添加回答
举报