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 關注
- 142 瀏覽
添加回答
舉報