出現(xiàn)問(wèn)題時(shí),重要的是提供信息來(lái)幫助診斷問(wèn)題。集成開(kāi)發(fā)環(huán)境 (IDE) 或調(diào)試器可以極大地幫助實(shí)現(xiàn)此效果,但它通常僅在開(kāi)發(fā)期間可用。應(yīng)用程序交付后,應(yīng)用程序本身必須收集和記錄診斷信息。為了滿足此要求,.NET 提供了一組工具來(lái)記錄診斷信息、監(jiān)視應(yīng)用程序行為、檢測(cè)運(yùn)行時(shí)錯(cuò)誤以及與調(diào)試工具(如果可用)集成。
某些診斷工具和 API 是特定于 Windows 的,因?yàn)樗鼈円蕾囉?Windows 操作系統(tǒng)的功能。為了防止特定于平臺(tái)的 API 使 .NET BCL 混亂,Microsoft 已將它們放在可以選擇引用的單獨(dú) NuGet 中。有十幾個(gè)特定于Windows的軟件包,您可以使用 “master”軟件包一次引用所有軟件包。
本章中的類型主要在 System.Diagnostics 中定義。
可以使用有條件地編譯 C# 中的任何代碼部分。預(yù)處理器指令是編譯器的特殊指令,以 # 符號(hào)開(kāi)頭(與其他 C# 構(gòu)造不同,它必須出現(xiàn)在自己的行上)。從邏輯上講,它們?cè)谥骶幾g發(fā)生之前執(zhí)行(盡管在實(shí)踐中,編譯器在詞法解析階段處理它們)。條件編譯的預(yù)處理器指令是 #if、#else、#endif 和 #elif。
#if 指令指示編譯器忽略一段代碼,除非定義了指定的。可以使用 #define 指令在源代碼中定義符號(hào)(在這種情況下,符號(hào)僅適用于該文件),也可以使用 <DefineConstants> 元素在 文件中定義符號(hào)(在這種情況下,符號(hào)應(yīng)用于整個(gè)程序集):
#define TESTMODE // #define directives must be at top of file
// Symbol names are uppercase by convention.
using System;
class Program
{
static void Main()
{
#if TESTMODE
Console.WriteLine ("in test mode!"); // OUTPUT: in test mode!
#endif
}
}
如果我們刪除第一行,程序?qū)⑹褂脧目蓤?zhí)行文件中完全刪除的 Console.WriteLine 語(yǔ)句進(jìn)行編譯,就好像它被注釋掉了一樣。
#else 語(yǔ)句類似于 C# 的 else 語(yǔ)句,#elif 等效于 #else 后跟 #if 。的 ||、 && 和 !運(yùn)算符執(zhí)行 和 :
#if TESTMODE && !PLAYMODE // if TESTMODE and not PLAYMODE
...
但請(qǐng)記住,您不是在構(gòu)建普通的 C# 表達(dá)式,并且您操作的符號(hào)與(靜態(tài)或其他)完全沒(méi)有聯(lián)系。
可以通過(guò)編輯 文件(或在 Visual Studio 中轉(zhuǎn)到“項(xiàng)目屬性”窗口中的“生成”選項(xiàng)卡)來(lái)定義應(yīng)用于程序集中每個(gè)文件的符號(hào)。下面定義了兩個(gè)常量,測(cè)試模式和播放模式:
<PropertyGroup>
<DefineConstants>TESTMODE;PLAYMODE</DefineConstants>
</PropertyGroup>
如果在程序集級(jí)別定義了一個(gè)符號(hào),然后想要為特定文件“取消定義”它,則可以使用 #undef 指令執(zhí)行此操作。
您可以改為使用簡(jiǎn)單的靜態(tài)字段實(shí)現(xiàn)前面的示例:
static internal bool TestMode=true;
static void Main()
{
if (TestMode) Console.WriteLine ("in test mode!");
}
這具有允許運(yùn)行時(shí)配置的優(yōu)點(diǎn)。那么,為什么選擇條件編譯呢?原因是條件編譯可以帶你去變量標(biāo)志不能的地方,例如:
您甚至可以在條件編譯指令下執(zhí)行重大重構(gòu),因此您可以立即在新舊版本之間切換,并編寫(xiě)可針對(duì)多個(gè)運(yùn)行時(shí)版本進(jìn)行編譯的庫(kù),利用可用的最新功能。
條件編譯的另一個(gè)優(yōu)點(diǎn)是調(diào)試代碼可以引用程序集中未包含在部署中的類型。
Condition 屬性指示編譯器忽略對(duì)特定類或方法的任何調(diào)用(如果尚未定義指定的符號(hào))。
若要了解這有何用處,假設(shè)您編寫(xiě)了一個(gè)用于記錄狀態(tài)信息的方法,如下所示:
static void LogStatus (string msg)
{
string logFilePath=...
System.IO.File.AppendAllText (logFilePath, msg + "\r\n");
}
現(xiàn)在假設(shè)您希望僅在定義了 LOGGINGMODE 符號(hào)時(shí)才執(zhí)行此操作。第一個(gè)解決方案是對(duì) LogStatus 的所有調(diào)用都圍繞一個(gè) #if 指令:
#if LOGGINGMODE
LogStatus ("Message Headers: " + GetMsgHeaders());
#endif
這給出了一個(gè)理想的結(jié)果,但它很乏味。第二種解決方案是將 #if 指令放在 LogStatus 方法中。但是,如果按如下方式調(diào)用 ,這是有問(wèn)題的:
LogStatus ("Message Headers: " + GetComplexMessageHeaders());
GetComplexMessageHeaders 將始終被調(diào)用,這可能會(huì)導(dǎo)致性能下降。
我們可以通過(guò)將條件屬性(在 System.Diagnostics 中定義)附加到 LogStatus 方法,將第一個(gè)解決方案的功能與第二個(gè)解決方案的便利性結(jié)合起來(lái):
[Conditional ("LOGGINGMODE")]
static void LogStatus (string msg)
{
...
}
這將指示編譯器將對(duì) LogStatus 的調(diào)用視為包裝在 #if LOGGINGMODE 指令中。如果未定義該符號(hào),則在編譯中完全消除對(duì) LogStatus 的任何調(diào)用,包括其參數(shù)計(jì)算表達(dá)式。(因此,將繞過(guò)任何副作用表達(dá)式。即使 LogStatus 和調(diào)用方位于不同的程序集中,這也有效。
[條件] 的另一個(gè)好處是,條件性檢查是在編譯調(diào)用時(shí)執(zhí)行的,而不是在編譯時(shí)執(zhí)行的。這是有益的,因?yàn)樗试S您編寫(xiě)包含 LogStatus 等方法的庫(kù),并且只構(gòu)建該庫(kù)的一個(gè)版本。
Conditional 屬性在運(yùn)行時(shí)被忽略,它純粹是對(duì)編譯器的指令。
如果需要在運(yùn)行時(shí)動(dòng)態(tài)啟用或禁用功能,則 Condition 屬性毫無(wú)用處:相反,必須使用基于變量的方法。這就留下了一個(gè)問(wèn)題,即在調(diào)用條件日志記錄方法時(shí)如何優(yōu)雅地規(guī)避參數(shù)的計(jì)算。函數(shù)式方法可以解決此問(wèn)題:
using System;
using System.Linq;
class Program
{
public static bool EnableLogging;
static void LogStatus (Func<string> message)
{
string logFilePath=...
if (EnableLogging)
System.IO.File.AppendAllText (logFilePath, message() + "\r\n");
}
}
lambda 表達(dá)式允許您調(diào)用此方法而不會(huì)造成語(yǔ)法膨脹:
LogStatus ( ()=> "Message Headers: " + GetComplexMessageHeaders() );
如果 EnableLogging 為 false,則永遠(yuǎn)不會(huì)計(jì)算 GetComplexMessageHeaders。
調(diào)試和跟蹤是提供基本日志記錄和斷言功能的靜態(tài)類。這兩個(gè)類非常相似;主要區(qū)別在于它們的預(yù)期用途。Debug 類用于調(diào)試版本;Trace 類適用于調(diào)試和發(fā)布版本。為此:
All methods of the Debug class are defined with [Conditional("DEBUG")].
All methods of the Trace class are defined with [Conditional("TRACE")].
這意味著編譯器將消除對(duì)調(diào)試或跟蹤所做的所有調(diào)用,除非您定義了 DEBUG 或 TRACE 符號(hào)。(Visual Studio 提供了用于在“項(xiàng)目屬性”的“生成”選項(xiàng)卡中定義這些符號(hào)的復(fù)選框,默認(rèn)情況下,新項(xiàng)目啟用 TRACE 符號(hào)。
Debug 和 Trace 類都提供 Write、WriteLine 和 WriteIf 方法。默認(rèn)情況下,這些消息將消息發(fā)送到調(diào)試器的輸出窗口:
Debug.Write ("Data");
Debug.WriteLine (23 * 34);
int x=5, y=3;
Debug.WriteIf (x > y, "x is greater than y");
Trace 類還提供了 TraceInformation 、TraceWarning 和 TraceError 等方法。這些方法和 Write 方法之間的行為差異取決于活動(dòng)的 TraceListener(我們?cè)谥袑?duì)此進(jìn)行了介紹)。
調(diào)試類和跟蹤類都提供 Fail 和斷言方法。Fail 將消息發(fā)送到調(diào)試或跟蹤類的偵聽(tīng)器集合中的每個(gè) TraceListener(請(qǐng)參閱下一節(jié)),默認(rèn)情況下,該集合將消息寫(xiě)入調(diào)試輸出:
Debug.Fail ("File data.txt does not exist!");
如果 bool 參數(shù)為 false,則 Assert 只是調(diào)用 Fail,這稱為,并在違反時(shí)指示代碼中的錯(cuò)誤。指定失敗消息是可選的:
Debug.Assert (File.Exists ("data.txt"), "File data.txt does not exist!");
var result=...
Debug.Assert (result !=null);
寫(xiě)入、失敗和斷言方法也會(huì)重載,以接受除消息之外的字符串類別,這在處理輸出時(shí)很有用。
斷言的替代方法是在相反條件為真時(shí)引發(fā)異常。這是驗(yàn)證方法參數(shù)時(shí)的常見(jiàn)做法:
public void ShowMessage (string message)
{
if (message==null) throw new ArgumentNullException ("message");
...
}
此類“斷言”是無(wú)條件編譯的,靈活性較低,因?yàn)槟鸁o(wú)法通過(guò) TraceListener 控制斷言失敗的結(jié)果。從技術(shù)上講,它們不是斷言。斷言是如果違反,則指示當(dāng)前方法代碼中的錯(cuò)誤。基于參數(shù)驗(yàn)證引發(fā)異常表示代碼中存在 bug。
Trace 類具有一個(gè)靜態(tài) Listeners 屬性,該屬性返回 TraceListener 實(shí)例的集合。它們負(fù)責(zé)處理由寫(xiě)入、失敗和跟蹤方法發(fā)出的內(nèi)容。
默認(rèn)情況下,每個(gè)偵聽(tīng)器集合都包含一個(gè)偵聽(tīng)器 ( 默認(rèn)跟蹤偵聽(tīng)器 )。默認(rèn)偵聽(tīng)器具有兩個(gè)主要功能:
可以通過(guò)(可選)刪除默認(rèn)偵聽(tīng)器,然后添加一個(gè)或多個(gè)自己的偵聽(tīng)器來(lái)更改此行為。您可以從頭開(kāi)始編寫(xiě)跟蹤偵聽(tīng)器(通過(guò)子類化 TraceListener ),也可以使用預(yù)定義的類型之一:
TextWriterTraceListener 進(jìn)一步子類化為 ConsoleTraceListener、 、XmlWriterTraceListener 和 EventSchemaTraceListener。
下面的示例清除 Trace 的默認(rèn)偵聽(tīng)器,然后添加三個(gè)偵聽(tīng)器,一個(gè)追加到文件,一個(gè)寫(xiě)入控制臺(tái),另一個(gè)寫(xiě)入 Windows 事件日志:
// Clear the default listener:
Trace.Listeners.Clear();
// Add a writer that appends to the trace.txt file:
Trace.Listeners.Add (new TextWriterTraceListener ("trace.txt"));
// Obtain the Console's output stream, then add that as a listener:
System.IO.TextWriter tw=Console.Out;
Trace.Listeners.Add (new TextWriterTraceListener (tw));
// Set up a Windows Event log source and then create/add listener.
// CreateEventSource requires administrative elevation, so this would
// typically be done in application setup.
if (!EventLog.SourceExists ("DemoApp"))
EventLog.CreateEventSource ("DemoApp", "Application");
Trace.Listeners.Add (new EventLogTraceListener ("DemoApp"));
對(duì)于 Windows 事件日志,使用 寫(xiě)入 、 失敗 或 斷言 方法編寫(xiě)的消息始終在 Windows 事件查看器中顯示為“信息”消息。但是,通過(guò) TraceWarning 和 TraceError 方法編寫(xiě)的消息將顯示為警告或錯(cuò)誤。
TraceListener 還具有 TraceFilter 類型的篩選器,您可以設(shè)置該篩選器以控制是否將消息寫(xiě)入該偵聽(tīng)器。為此,您可以實(shí)例化預(yù)定義的子類之一( 事件類型篩選器 或 源篩選器 ),或者子類 TraceFilter 并重寫(xiě) ShouldTrace 方法。例如,您可以使用它按類別進(jìn)行過(guò)濾。
TraceListener 還定義了用于控制縮進(jìn)的 IndentLevel 和 IndentSize 屬性,以及用于寫(xiě)入額外數(shù)據(jù)的 TraceOutputOptions 屬性:
TextWriterTraceListener tl=new TextWriterTraceListener (Console.Out);
tl.TraceOutputOptions=TraceOptions.DateTime | TraceOptions.Callstack;
使用 Trace 方法時(shí)應(yīng)用跟蹤輸出選項(xiàng):
Trace.TraceWarning ("Orange alert");
DiagTest.vshost.exe Warning: 0 : Orange alert
DateTime=2007-03-08T05:57:13.6250000Z
Callstack=at System.Environment.GetStackTrace(Exception e, Boolean
needFileInfo)
at System.Environment.get_StackTrace() at ...
某些偵聽(tīng)器(如 TextWriterTraceListener)最終會(huì)寫(xiě)入受緩存影響的流。這有兩個(gè)含義:
Trace 和 Debug 類提供靜態(tài)的 Close 和 Flush 方法,這些方法在所有偵聽(tīng)器上調(diào)用 Close 或 Flush(而偵聽(tīng)器又在任何基礎(chǔ)器和流上調(diào)用 Close 或 Flush)。Close 隱式調(diào)用 Flush ,關(guān)閉文件句柄,并防止寫(xiě)入進(jìn)一步的數(shù)據(jù)。
作為一般規(guī)則,在應(yīng)用程序結(jié)束之前調(diào)用 Close,并隨時(shí)調(diào)用 Flush 以確保寫(xiě)入當(dāng)前消息數(shù)據(jù)。這適用于使用基于流或文件的偵聽(tīng)器。
跟蹤和調(diào)試還提供自動(dòng)刷新屬性,如果為 true,則在每條消息后強(qiáng)制刷新。
如果使用任何基于文件或流的偵聽(tīng)器,則最好在“調(diào)試和跟蹤”上將自動(dòng)刷新設(shè)置為 true。否則,如果發(fā)生未經(jīng)處理的異常或嚴(yán)重錯(cuò)誤,最后 4 KB 的診斷信息可能會(huì)丟失。
有時(shí),應(yīng)用程序與調(diào)試器交互(如果可用)很有用。在開(kāi)發(fā)過(guò)程中,調(diào)試器通常是您的IDE(例如,Visual Studio);在部署中,調(diào)試器更可能是較低級(jí)別的調(diào)試工具之一,例如 WinDbg、Cordbg 或 MDbg。
System.Diagnostics中的靜態(tài)調(diào)試器類提供了與調(diào)試器交互的基本函數(shù),即中斷、啟動(dòng)、日志和IsAttached 。
調(diào)試器必須首先附加到應(yīng)用程序才能對(duì)其進(jìn)行調(diào)試。如果從 IDE 中啟動(dòng)應(yīng)用程序,則會(huì)自動(dòng)執(zhí)行此操作,除非您另有請(qǐng)求(通過(guò)選擇“啟動(dòng)但不調(diào)試”)。但是,有時(shí)在 IDE 中以調(diào)試模式啟動(dòng)應(yīng)用程序是不方便或不可能的。一個(gè)例子是Windows Service應(yīng)用程序或(具有諷刺意味的)Visual Studio設(shè)計(jì)器。一種解決方案是正常啟動(dòng)應(yīng)用程序,然后在 IDE 中選擇“調(diào)試進(jìn)程”。但是,這不允許在程序執(zhí)行的早期設(shè)置斷點(diǎn)。
解決方法是從應(yīng)用程序內(nèi)部調(diào)用 Debugger.Break。此方法啟動(dòng)調(diào)試器,附加到該調(diào)試器,并在該點(diǎn)掛起執(zhí)行。(啟動(dòng)執(zhí)行相同的操作,但不暫停執(zhí)行。附加后,可以使用 Log 方法將消息直接記錄到調(diào)試器的輸出窗口。可以通過(guò)檢查 IsAttached 屬性來(lái)驗(yàn)證是否已附加到調(diào)試器。
DebuggerStepThrough 和 DebuggerHidden 屬性向調(diào)試器提供有關(guān)如何處理特定方法、構(gòu)造函數(shù)或類的單步執(zhí)行的建議。
調(diào)試器單步執(zhí)行請(qǐng)求調(diào)試器在沒(méi)有任何用戶交互的情況下單步執(zhí)行函數(shù)。此屬性在自動(dòng)生成的方法和將實(shí)際工作轉(zhuǎn)發(fā)到其他位置的方法的代理方法中很有用。在后一種情況下,如果在“real”方法中設(shè)置了斷點(diǎn),調(diào)試器仍將在調(diào)用堆棧中顯示代理方法,除非還添加了 DebuggerHidden 屬性。可以在代理上組合這兩個(gè)屬性,以幫助用戶專注于調(diào)試應(yīng)用程序邏輯而不是管道:
[DebuggerStepThrough, DebuggerHidden]
void DoWorkProxy()
{
// setup...
DoWork();
// teardown...
}
void DoWork() {...} // Real method...
我們?cè)诘淖詈笠还?jié)中描述了如何使用 Process.Start 啟動(dòng)一個(gè)新進(jìn)程。Process 類還允許您查詢?cè)谕慌_(tái)或另一臺(tái)計(jì)算機(jī)上運(yùn)行的其他進(jìn)程并與之交互。Process 類是 .NET 標(biāo)準(zhǔn) 2.0 的一部分,盡管其功能僅限于 UWP 平臺(tái)。
這些方法按名稱或進(jìn)程 ID 檢索特定進(jìn)程,或檢索當(dāng)前計(jì)算機(jī)或指定計(jì)算機(jī)上運(yùn)行的所有進(jìn)程。這包括托管和非托管進(jìn)程。每個(gè) Process 實(shí)例都有豐富的屬性映射統(tǒng)計(jì)信息,例如名稱、ID、優(yōu)先級(jí)、內(nèi)存和處理器利用率、窗口句柄等。下面的示例枚舉當(dāng)前計(jì)算機(jī)上所有正在運(yùn)行的進(jìn)程:Process.GetProcessXXX
foreach (Process p in Process.GetProcesses())
using (p)
{
Console.WriteLine (p.ProcessName);
Console.WriteLine (" PID: " + p.Id);
Console.WriteLine (" Memory: " + p.WorkingSet64);
Console.WriteLine (" Threads: " + p.Threads.Count);
}
Process.GetCurrentProcess 返回當(dāng)前進(jìn)程。
可以通過(guò)調(diào)用進(jìn)程的 Kill 方法來(lái)終止進(jìn)程。
還可以使用 Process.Threads 屬性枚舉其他進(jìn)程的線程。但是,您獲得的對(duì)象不是 System.Threading.Thread 對(duì)象;它們是 ProcessThread 對(duì)象,用于管理任務(wù)而不是同步任務(wù)。ProcessThread 對(duì)象提供有關(guān)基礎(chǔ)線程的診斷信息,并允許您控制它的某些方面,例如其優(yōu)先級(jí)和處理器關(guān)聯(lián)性:
public void EnumerateThreads (Process p)
{
foreach (ProcessThread pt in p.Threads)
{
Console.WriteLine (pt.Id);
Console.WriteLine (" State: " + pt.ThreadState);
Console.WriteLine (" Priority: " + pt.PriorityLevel);
Console.WriteLine (" Started: " + pt.StartTime);
Console.WriteLine (" CPU time: " + pt.TotalProcessorTime);
}
}
類提供執(zhí)行調(diào)用堆棧的只讀視圖。您可以獲取當(dāng)前線程或異常對(duì)象的堆棧跟蹤。此類信息主要用于診斷目的,盡管您也可以在編程(黑客)中使用它。堆棧跟蹤表示一個(gè)完整的調(diào)用堆棧;StackFrame 表示該堆棧中的單個(gè)方法調(diào)用。
如果只需要知道調(diào)用方法的名稱和行號(hào),則調(diào)用方信息屬性可以提供更簡(jiǎn)單、更快捷的替代方法。我們將在第 的中介紹此主題。
如果實(shí)例化不帶參數(shù)的 StackTrace 對(duì)象(或使用布爾參數(shù)),則會(huì)獲得當(dāng)前線程調(diào)用堆棧的快照。如果為 true,則 bool 參數(shù)指示 StackTrace 讀取程序集(項(xiàng)目調(diào)試)文件(如果存在),從而使您能夠訪問(wèn)文件名、行號(hào)和列偏移量數(shù)據(jù)。使用 /debug 開(kāi)關(guān)進(jìn)行編譯時(shí),將生成項(xiàng)目調(diào)試文件。(Visual Studio 使用此開(kāi)關(guān)進(jìn)行編譯,除非您通過(guò)提出其他請(qǐng)求。
獲得 StackTrace 后,您可以通過(guò)調(diào)用 GetFrame 來(lái)檢查特定幀,或者使用 GetFrame 獲取整個(gè)幀:
static void Main() { A (); }
static void A() { B (); }
static void B() { C (); }
static void C()
{
StackTrace s=new StackTrace (true);
Console.WriteLine ("Total frames: " + s.FrameCount);
Console.WriteLine ("Current method: " + s.GetFrame(0).GetMethod().Name);
Console.WriteLine ("Calling method: " + s.GetFrame(1).GetMethod().Name);
Console.WriteLine ("Entry method: " + s.GetFrame
(s.FrameCount-1).GetMethod().Name);
Console.WriteLine ("Call Stack:");
foreach (StackFrame f in s.GetFrames())
Console.WriteLine (
" File: " + f.GetFileName() +
" Line: " + f.GetFileLineNumber() +
" Col: " + f.GetFileColumnNumber() +
" Offset: " + f.GetILOffset() +
" Method: " + f.GetMethod().Name);
}
下面是輸出:
Total frames: 4
Current method: C
Calling method: B
Entry method: Main
Call stack:
File: C:\Test\Program.cs Line: 15 Col: 4 Offset: 7 Method: C
File: C:\Test\Program.cs Line: 12 Col: 22 Offset: 6 Method: B
File: C:\Test\Program.cs Line: 11 Col: 22 Offset: 6 Method: A
File: C:\Test\Program.cs Line: 10 Col: 25 Offset: 6 Method: Main
中間語(yǔ)言 (IL) 偏移量表示將執(zhí)行的指令的偏移量,而不是當(dāng)前正在執(zhí)行的指令的偏移量。但是,奇怪的是,行號(hào)和列號(hào)(如果存在文件)通常指示實(shí)際的執(zhí)行點(diǎn)。
發(fā)生這種情況是因?yàn)?CLR 在從 IL 偏移量計(jì)算行和列時(shí)會(huì)盡力實(shí)際執(zhí)行點(diǎn)。編譯器以這樣一種方式發(fā)出 IL,包括將 nop(無(wú)操作)指令插入 IL 流中。
但是,在啟用優(yōu)化的情況下進(jìn)行編譯會(huì)禁用 nop 指令的插入,因此堆棧跟蹤可能會(huì)顯示要執(zhí)行的下一條語(yǔ)句的行號(hào)和列號(hào)。由于優(yōu)化可能會(huì)拉出其他技巧(包括折疊整個(gè)方法),因此進(jìn)一步阻礙了獲取有用的堆棧跟蹤。
獲取整個(gè) StackTrace 基本信息的快捷方式是在其上調(diào)用 ToString。結(jié)果如下所示:
at DebugTest.Program.C() in C:\Test\Program.cs:line 16
at DebugTest.Program.B() in C:\Test\Program.cs:line 12
at DebugTest.Program.A() in C:\Test\Program.cs:line 11
at DebugTest.Program.Main() in C:\Test\Program.cs:line 10
您還可以通過(guò)將異常傳遞到 StackTrace 的中來(lái)獲取異常對(duì)象的堆棧跟蹤(顯示導(dǎo)致引發(fā)異常的原因)。
異常已具有 StackTrace 屬性;但是,此屬性返回一個(gè)簡(jiǎn)單的字符串,而不是 StackTrace 對(duì)象。StackTrace 對(duì)象在記錄部署后發(fā)生的異常(沒(méi)有文件)時(shí)要有用得多,因?yàn)槟梢杂涗?量來(lái)代替行號(hào)和列號(hào)。使用 IL 偏移量和 ,您可以查明方法中發(fā)生錯(cuò)誤的位置。
Win32 平臺(tái)以 Windows 事件日志的形式提供集中式日志記錄機(jī)制。
我們之前使用的調(diào)試和跟蹤類將寫(xiě)入 Windows 事件日志,如果您注冊(cè)了 EventLogTraceListener 。但是,使用 EventLog 類,可以直接寫(xiě)入 Windows 事件日志,而無(wú)需使用 Trace 或 Debug 。還可以使用此類來(lái)讀取和監(jiān)視事件數(shù)據(jù)。
寫(xiě)入 Windows 事件日志在 Windows 服務(wù)應(yīng)用程序中是有意義的,因?yàn)槿绻霈F(xiàn)問(wèn)題,則無(wú)法彈出用戶界面,將用戶定向到已寫(xiě)入診斷信息的某些特殊文件。此外,由于服務(wù)寫(xiě)入 Windows 事件日志是常見(jiàn)的做法,因此,如果服務(wù)失敗,管理員可能會(huì)首先查看此位置。
有三個(gè)標(biāo)準(zhǔn)的 Windows 事件日志,由以下名稱標(biāo)識(shí):
應(yīng)用程序日志是大多數(shù)應(yīng)用程序通常寫(xiě)入的位置。
寫(xiě)入 Windows 事件日志:
是應(yīng)用程序的易于識(shí)別的名稱。必須先注冊(cè)源名稱,然后才能使用它 - CreateEventSource 方法執(zhí)行此功能。然后,您可以調(diào)用 WriteEntry :
const string SourceName="MyCompany.WidgetServer";
// CreateEventSource requires administrative permissions, so this would
// typically be done in application setup.
if (!EventLog.SourceExists (SourceName))
EventLog.CreateEventSource (SourceName, "Application");
EventLog.WriteEntry (SourceName,
"Service started; using configuration file=...",
EventLogEntryType.Information);
事件日志條目類型可以是 信息 、 警告 、 錯(cuò)誤 、 成功審核 或 失敗審核 。每個(gè)事件在 Windows 事件查看器中都顯示不同的圖標(biāo)。您還可以選擇指定類別和事件 ID(每個(gè)都是您自己選擇的數(shù)字)并提供可選的二進(jìn)制數(shù)據(jù)。
CreateEventSource 還允許您指定計(jì)算機(jī)名稱:如果您有足夠的權(quán)限,則寫(xiě)入另一臺(tái)計(jì)算機(jī)的事件日志。
若要讀取事件日志,請(qǐng)使用要訪問(wèn)的日志的名稱和日志所在的另一臺(tái)計(jì)算機(jī)的名稱(可選)實(shí)例化 EventLog 類。然后,可以通過(guò) Entries 集合屬性讀取每個(gè)日志條目:
EventLog log=new EventLog ("Application");
Console.WriteLine ("Total entries: " + log.Entries.Count);
EventLogEntry last=log.Entries [log.Entries.Count - 1];
Console.WriteLine ("Index: " + last.Index);
Console.WriteLine ("Source: " + last.Source);
Console.WriteLine ("Type: " + last.EntryType);
Console.WriteLine ("Time: " + last.TimeWritten);
Console.WriteLine ("Message: " + last.Message);
您可以通過(guò)靜態(tài)方法 EventLog.GetEventLogs 枚舉當(dāng)前(或其他)計(jì)算機(jī)的所有日志(這需要管理權(quán)限才能進(jìn)行完全訪問(wèn)):
foreach (EventLog log in EventLog.GetEventLogs())
Console.WriteLine (log.LogDisplayName);
這通常至少打印。
每當(dāng)條目寫(xiě)入 Windows 事件日志時(shí),都可以通過(guò) EntryWrite 事件收到警報(bào)。這適用于本地計(jì)算機(jī)上的事件日志,無(wú)論哪個(gè)應(yīng)用程序記錄了事件,它都會(huì)觸發(fā)。
要啟用日志監(jiān)控:
例如:
using (var log=new EventLog ("Application"))
{
log.EnableRaisingEvents=true;
log.EntryWritten +=DisplayEntry;
Console.ReadLine();
}
void DisplayEntry (object sender, EntryWrittenEventArgs e)
{
EventLogEntry entry=e.Entry;
Console.WriteLine (entry.Message);
}
性能計(jì)數(shù)器是僅限 Windows 的功能,需要 NuGet 包 System.Diagnostics.PerformanceCounter 。如果您面向 Linux 或 macOS,請(qǐng)參閱了解替代方案。
到目前為止,我們討論的日志記錄機(jī)制對(duì)于捕獲信息以供將來(lái)分析非常有用。但是,要深入了解應(yīng)用程序(或整個(gè)系統(tǒng))的當(dāng)前狀態(tài),需要一種更實(shí)時(shí)的方法。滿足此需求的 Win32 解決方案是性能監(jiān)視基礎(chǔ)結(jié)構(gòu),它由系統(tǒng)和應(yīng)用程序公開(kāi)的一組性能計(jì)數(shù)器以及用于實(shí)時(shí)監(jiān)視這些計(jì)數(shù)器的Microsoft管理控制臺(tái) (MMC) 管理單元組成。
性能計(jì)數(shù)器分為“系統(tǒng)”、“處理器”、“.NET CLR 內(nèi)存”等類別。這些類別有時(shí)也被 GUI 工具稱為“性能對(duì)象”。每個(gè)類別對(duì)一組相關(guān)的性能計(jì)數(shù)器進(jìn)行分組,這些性能計(jì)數(shù)器監(jiān)視系統(tǒng)或應(yīng)用程序的一個(gè)方面。“.NET CLR 內(nèi)存”類別中的性能計(jì)數(shù)器示例包括“GC 中的時(shí)間百分比”、“所有堆中的 # 字節(jié)數(shù)”和“分配的字節(jié)/秒”。
每個(gè)類別可以選擇具有一個(gè)或多個(gè)可以獨(dú)立監(jiān)視的實(shí)例。例如,這在“處理器”類別中的“處理器時(shí)間百分比”性能計(jì)數(shù)器中很有用,它允許監(jiān)視 CPU 利用率。在多處理器計(jì)算機(jī)上,此計(jì)數(shù)器支持每個(gè) CPU 的實(shí)例,允許您獨(dú)立監(jiān)視每個(gè) CPU 的利用率。
以下各節(jié)演示如何執(zhí)行通常需要的任務(wù),例如確定公開(kāi)的計(jì)數(shù)器、監(jiān)視計(jì)數(shù)器以及創(chuàng)建自己的計(jì)數(shù)器以公開(kāi)應(yīng)用程序狀態(tài)信息。
讀取性能計(jì)數(shù)器或類別可能需要本地或目標(biāo)計(jì)算機(jī)上的管理員權(quán)限,具體取決于訪問(wèn)的內(nèi)容。
下面的示例枚舉計(jì)算機(jī)上所有可用的性能計(jì)數(shù)器。對(duì)于具有實(shí)例的用戶,它會(huì)枚舉每個(gè)實(shí)例的計(jì)數(shù)器:
PerformanceCounterCategory[] cats=PerformanceCounterCategory.GetCategories();
foreach (PerformanceCounterCategory cat in cats)
{
Console.WriteLine ("Category: " + cat.CategoryName);
string[] instances=cat.GetInstanceNames();
if (instances.Length==0)
{
foreach (PerformanceCounter ctr in cat.GetCounters())
Console.WriteLine (" Counter: " + ctr.CounterName);
}
else // Dump counters with instances
{
foreach (string instance in instances)
{
Console.WriteLine (" Instance: " + instance);
if (cat.InstanceExists (instance))
foreach (PerformanceCounter ctr in cat.GetCounters (instance))
Console.WriteLine (" Counter: " + ctr.CounterName);
}
}
}
結(jié)果是超過(guò) 10,000 行長(zhǎng)!執(zhí)行也需要一段時(shí)間,因?yàn)?PerformanceCounterCategory.InstanceExists 的實(shí)現(xiàn)效率低下。在實(shí)際系統(tǒng)中,您只希望按需檢索更詳細(xì)的信息。
下一個(gè)示例使用 LINQ 僅檢索 .NET 性能計(jì)數(shù)器,并將結(jié)果寫(xiě)入 XML 文件:
var x=new XElement ("counters",
from PerformanceCounterCategory cat in
PerformanceCounterCategory.GetCategories()
where cat.CategoryName.StartsWith (".NET")
let instances=cat.GetInstanceNames()
select new XElement ("category",
new XAttribute ("name", cat.CategoryName),
instances.Length==0
?
from c in cat.GetCounters()
select new XElement ("counter",
new XAttribute ("name", c.CounterName))
:
from i in instances
select new XElement ("instance", new XAttribute ("name", i),
!cat.InstanceExists (i)
?
null
:
from c in cat.GetCounters (i)
select new XElement ("counter",
new XAttribute ("name", c.CounterName))
)
)
);
x.Save ("counters.xml");
若要檢索性能計(jì)數(shù)器的值,請(qǐng)實(shí)例化性能計(jì)數(shù)器對(duì)象,然后調(diào)用 NextValue 或 NextSample 方法。下一個(gè)值返回一個(gè)簡(jiǎn)單的浮點(diǎn)值;NextSample 返回一個(gè) CounterSample 對(duì)象,該對(duì)象公開(kāi)一組更高級(jí)的屬性,例如 CounterFrequency 、TimeStamp 、BaseValue 和 RawValue 。
PerformanceCounter 的構(gòu)造函數(shù)采用類別名稱、計(jì)數(shù)器名稱和可選實(shí)例。因此,要顯示所有 CPU 的當(dāng)前處理器利用率,請(qǐng)執(zhí)行以下操作:
using PerformanceCounter pc=new PerformanceCounter ("Processor",
"% Processor Time",
"_Total");
Console.WriteLine (pc.NextValue());
或者顯示當(dāng)前進(jìn)程的“真實(shí)”(即私有)內(nèi)存消耗:
string procName=Process.GetCurrentProcess().ProcessName;
using PerformanceCounter pc=new PerformanceCounter ("Process",
"Private Bytes",
procName);
Console.WriteLine (pc.NextValue());
性能計(jì)數(shù)器不會(huì)公開(kāi) ValueChanged 事件,因此如果要監(jiān)視更改,則必須輪詢。在下一個(gè)示例中,我們每 200 毫秒輪詢一次,直到 EventWaitHandle 發(fā)出退出信號(hào):
// need to import System.Threading as well as System.Diagnostics
static void Monitor (string category, string counter, string instance,
EventWaitHandle stopper)
{
if (!PerformanceCounterCategory.Exists (category))
throw new InvalidOperationException ("Category does not exist");
if (!PerformanceCounterCategory.CounterExists (counter, category))
throw new InvalidOperationException ("Counter does not exist");
if (instance==null) instance=""; // ""==no instance (not null!)
if (instance !="" &&
!PerformanceCounterCategory.InstanceExists (instance, category))
throw new InvalidOperationException ("Instance does not exist");
float lastValue=0f;
using (PerformanceCounter pc=new PerformanceCounter (category,
counter, instance))
while (!stopper.WaitOne (200, false))
{
float value=pc.NextValue();
if (value !=lastValue) // Only write out the value
{ // if it has changed.
Console.WriteLine (value);
lastValue=value;
}
}
}
以下是我們?nèi)绾问褂么朔椒ㄍ瑫r(shí)監(jiān)視處理器和硬盤(pán)驅(qū)動(dòng)器活動(dòng):
EventWaitHandle stopper=new ManualResetEvent (false);
new Thread (()=>
Monitor ("Processor", "% Processor Time", "_Total", stopper)
).Start();
new Thread (()=>
Monitor ("LogicalDisk", "% Idle Time", "C:", stopper)
).Start();
Console.WriteLine ("Monitoring - press any key to quit");
Console.ReadKey();
stopper.Set();
在寫(xiě)入性能計(jì)數(shù)器數(shù)據(jù)之前,需要?jiǎng)?chuàng)建性能類別和計(jì)數(shù)器。您必須在一個(gè)步驟中創(chuàng)建性能類別以及屬于它的所有計(jì)數(shù)器,如下所示:
string category="Nutshell Monitoring";
// We'll create two counters in this category:
string eatenPerMin="Macadamias eaten so far";
string tooHard="Macadamias deemed too hard";
if (!PerformanceCounterCategory.Exists (category))
{
CounterCreationDataCollection cd=new CounterCreationDataCollection();
cd.Add (new CounterCreationData (eatenPerMin,
"Number of macadamias consumed, including shelling time",
PerformanceCounterType.NumberOfItems32));
cd.Add (new CounterCreationData (tooHard,
"Number of macadamias that will not crack, despite much effort",
PerformanceCounterType.NumberOfItems32));
PerformanceCounterCategory.Create (category, "Test Category",
PerformanceCounterCategoryType.SingleInstance, cd);
}
然后,當(dāng)您選擇“添加計(jì)數(shù)器”時(shí),新計(jì)數(shù)器將顯示在 Windows 性能監(jiān)視工具中。如果以后要在同一類別中定義更多計(jì)數(shù)器,則必須首先通過(guò)調(diào)用 PerformanceCounterCategory.Delete 來(lái)刪除舊類別。
創(chuàng)建和刪除性能計(jì)數(shù)器需要管理權(quán)限。因此,它通常作為應(yīng)用程序設(shè)置的一部分完成。
創(chuàng)建計(jì)數(shù)器后,可以通過(guò)實(shí)例化性能計(jì)數(shù)器、將“只讀”設(shè)置為 false 以及設(shè)置 RawValue 來(lái)更新其值。還可以使用 Increment 和 IncrementBy 方法來(lái)更新現(xiàn)有值:
string category="Nutshell Monitoring";
string eatenPerMin="Macadamias eaten so far";
using (PerformanceCounter pc=new PerformanceCounter (category,
eatenPerMin, ""))
{
pc.ReadOnly=false;
pc.RawValue=1000;
pc.Increment();
pc.IncrementBy (10);
Console.WriteLine (pc.NextValue()); // 1011
}
秒表類為測(cè)量執(zhí)行時(shí)間提供了一種方便的機(jī)制。秒表使用操作系統(tǒng)和硬件提供的最高分辨率機(jī)制,通常小于一微秒。(相比之下,DateTime.Now 和 Environment.TickCount 的分辨率約為 15 毫秒。
要使用秒表,請(qǐng)調(diào)用 StartNew — 這將實(shí)例化秒表并啟動(dòng)它滴答作響。(或者,您可以手動(dòng)實(shí)例化它,然后調(diào)用“開(kāi)始”。已用屬性以 TimeSpan 的形式返回已用間隔:
Stopwatch s=Stopwatch.StartNew();
System.IO.File.WriteAllText ("test.txt", new string ('*', 30000000));
Console.WriteLine (s.Elapsed); // 00:00:01.4322661
秒表還公開(kāi)了一個(gè) ElapsedTicks 屬性,該屬性以 long 的形式返回已用的“ticks”數(shù)。要將刻度轉(zhuǎn)換為秒,請(qǐng)除以秒表。還有一個(gè) ElapsedMilliseconds 屬性,它通常是最方便的。
調(diào)用停止會(huì)凍結(jié)已用和已用的滴答聲。“正在運(yùn)行”的秒表不會(huì)產(chǎn)生后臺(tái)活動(dòng),因此調(diào)用 Stop 是可選的。
在本部分中,我們將簡(jiǎn)要介紹可用于 .NET 的跨平臺(tái)診斷工具:
dotnet-counters
提供正在運(yùn)行的應(yīng)用程序的狀態(tài)概述
dotnet-trace
有關(guān)更詳細(xì)的性能和事件監(jiān)控
dotnet-dump
按需或在崩潰后獲取內(nèi)存轉(zhuǎn)儲(chǔ)
這些工具不需要管理提升,適用于開(kāi)發(fā)和生產(chǎn)環(huán)境。
工具監(jiān)視 .NET 進(jìn)程的內(nèi)存和 CPU 使用率,并將數(shù)據(jù)寫(xiě)入控制臺(tái)(或文件)。
若要安裝該工具,請(qǐng)從命令提示符或路徑中帶有 的終端運(yùn)行以下命令:
dotnet tool install --global dotnet-counters
然后,您可以開(kāi)始監(jiān)視進(jìn)程,如下所示:
dotnet-counters monitor System.Runtime --process-id <<ProcessID>>
System.Runtime 意味著我們要監(jiān)視 類別下的所有計(jì)數(shù)器。可以指定類別或計(jì)數(shù)器名稱(dotnet 計(jì)數(shù)器列表命令列出所有可用的類別和計(jì)數(shù)器)。
輸出會(huì)不斷刷新,如下所示:
Press p to pause, r to resume, q to quit.
Status: Running
[System.Runtime]
# of Assemblies Loaded 63
% Time in GC (since last GC) 0
Allocation Rate (Bytes / sec) 244,864
CPU Usage (%) 6
Exceptions / sec 0
GC Heap Size (MB) 8
Gen 0 GC / sec 0
Gen 0 Size (B) 265,176
Gen 1 GC / sec 0
Gen 1 Size (B) 451,552
Gen 2 GC / sec 0
Gen 2 Size (B) 24
LOH Size (B) 3,200,296
Monitor Lock Contention Count / sec 0
Number of Active Timers 0
ThreadPool Completed Work Items / sec 15
ThreadPool Queue Length 0
ThreadPool Threads Count 9
Working Set (MB) 52
以下是所有可用的命令:
命令 | 目的 |
列表 | 顯示計(jì)數(shù)器名稱列表以及每個(gè)計(jì)數(shù)器名稱的說(shuō)明 |
附言 | 顯示符合監(jiān)視條件的 dotnet 進(jìn)程的列表 |
監(jiān)控 | 顯示所選計(jì)數(shù)器的值(定期刷新) |
收集 | 將計(jì)數(shù)器信息保存到文件中 |
支持以下參數(shù):
選項(xiàng)/參數(shù) | 目的 |
--版本 | 顯示的版本。 |
-h, --幫助 | 顯示有關(guān)程序的幫助。 |
-p, --進(jìn)程標(biāo)識(shí) | 要監(jiān)視的 dotnet 進(jìn)程的 ID。適用于監(jiān)視器和收集命令。 |
--刷新間隔 | 設(shè)置所需的刷新間隔(以秒為單位)。適用于監(jiān)視器和收集命令。 |
-o, --輸出 | 設(shè)置輸出文件名。適用于收集命令。 |
--格式 | 設(shè)置輸出格式。有效的是 或 。適用于收集命令。 |
跟蹤是程序中事件的時(shí)間戳記錄,例如正在調(diào)用的方法或正在查詢的數(shù)據(jù)庫(kù)。跟蹤還可以包括性能指標(biāo)和自定義事件,并且可以包含局部上下文,例如局部變量的值。傳統(tǒng)上,.NET Framework 和框架(如 ASP.NET)使用 ETW。在 .NET 5 中,應(yīng)用程序跟蹤在 Windows 上運(yùn)行時(shí)寫(xiě)入 ETW,在 Linux 上運(yùn)行時(shí)寫(xiě)入 LTTng。
若要安裝該工具,請(qǐng)運(yùn)行以下命令:
dotnet tool install --global dotnet-trace
若要開(kāi)始記錄程序的事件,請(qǐng)運(yùn)行以下命令:
dotnet-trace collect --process-id <<ProcessId>>
這將使用默認(rèn)配置文件運(yùn)行 ,該配置文件收集 CPU 和 .NET 運(yùn)行時(shí)事件并寫(xiě)入名為 的文件。您可以使用 --profile 開(kāi)關(guān)指定其他配置文件:gc-verbose 跟蹤垃圾回收和采樣對(duì)象分配, 以較低的開(kāi)銷跟蹤垃圾回收。-o 開(kāi)關(guān)允許您指定不同的輸出文件名。
默認(rèn)輸出是一個(gè) 文件,可以使用 PerfView 工具直接在 Windows 計(jì)算機(jī)上進(jìn)行分析。或者,您可以指示創(chuàng)建與Speedscope兼容的文件,Speedscope是 的免費(fèi)在線分析服務(wù)。要?jiǎng)?chuàng)建 Speedscope (.) 文件,請(qǐng)使用選項(xiàng) -。
您可以從 下載最新版本的 PerfView。Windows 10 附帶的版本可能不支持 文件。
支持以下命令:
命令 | 目的 |
收集 | 開(kāi)始將計(jì)數(shù)器信息記錄到文件中。 |
附言 | 顯示符合監(jiān)視條件的 dotnet 進(jìn)程的列表。 |
列表配置文件 | 列出預(yù)生成的跟蹤配置文件,并附有每個(gè)配置文件中的提供程序和篩選器的說(shuō)明。 |
轉(zhuǎn)換<文件> | 從 () 格式轉(zhuǎn)換為備用格式。目前,是唯一的目標(biāo)選項(xiàng)。 |
你的應(yīng)用可以通過(guò)定義自定義事件源來(lái)發(fā)出自定義事件:
[EventSource (Name="MyTestSource")]
public sealed class MyEventSource : EventSource
{
public static MyEventSource Instance=new MyEventSource ();
MyEventSource() : base (EventSourceSettings.EtwSelfDescribingEventFormat)
{
}
public void Log (string message, int someNumber)
{
WriteEvent (1, message, someNumber);
}
}
WriteEvent 方法被重載以接受簡(jiǎn)單類型(主要是字符串和整數(shù))的各種組合。然后可以按如下方式調(diào)用它:
MyEventSource.Instance.Log ("Something", 123);
調(diào)用 時(shí),必須指定要記錄的任何自定義事件源的名稱:
dotnet-trace collect --process-id <<ProcessId>> --providers MyTestSource
轉(zhuǎn)儲(chǔ)(有時(shí)稱為)是進(jìn)程虛擬內(nèi)存狀態(tài)的快照。您可以按需轉(zhuǎn)儲(chǔ)正在運(yùn)行的進(jìn)程,也可以將操作系統(tǒng)配置為在應(yīng)用程序崩潰時(shí)生成轉(zhuǎn)儲(chǔ)。
在 Ubuntu Linux 上,以下命令在應(yīng)用程序崩潰時(shí)啟用核心轉(zhuǎn)儲(chǔ)(必要的步驟可能因不同版本的 Linux 而異):
ulimit -c unlimited
在 Windows 上,使用 在本地計(jì)算機(jī)配置單元中創(chuàng)建或編輯以下項(xiàng):
SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps
在此下,添加一個(gè)與可執(zhí)行文件同名的密鑰(例如 ),并在該密鑰下添加以下密鑰:
若要安裝該工具,請(qǐng)運(yùn)行以下命令:
dotnet tool install --global dotnet-dump
安裝后,可以按需啟動(dòng)轉(zhuǎn)儲(chǔ)(不結(jié)束該過(guò)程),如下所示:
dotnet-dump collect --process-id <<YourProcessId>>
以下命令啟動(dòng)用于分析轉(zhuǎn)儲(chǔ)文件的交互式 shell:
dotnet-dump analyze <<dumpfile>>
如果異常導(dǎo)致應(yīng)用程序關(guān)閉,則可以使用 命令(簡(jiǎn)稱 )顯示該異常的詳細(xì)信息。dotnet-dump shell 支持許多其他命令,您可以使用 命令列出這些命令。
注冊(cè)表是一個(gè)復(fù)雜的數(shù)據(jù)庫(kù),如果不進(jìn)行維護(hù),它就會(huì)填充損壞的和孤立的注冊(cè)表項(xiàng)。尤其是對(duì)Windows進(jìn)行升級(jí)時(shí),損壞或丟失的注冊(cè)表項(xiàng)也會(huì)不斷累積,從而影響您的系統(tǒng)性能。如果您的Windows 10系統(tǒng)正在經(jīng)歷注冊(cè)表?yè)p壞的問(wèn)題,不妨借助本文給大家介紹的幾種方式進(jìn)行修復(fù),以防止計(jì)算機(jī)的性能不斷受損。下面我們就去深入了解一下修復(fù)方法吧。
在較新的Windows系統(tǒng)版本中(比如Windows 10/11等),您可以借助啟動(dòng)修復(fù)功能修復(fù)損壞的注冊(cè)表。具體操作步驟如下:
步驟1:運(yùn)行計(jì)算機(jī),在左下角搜索框內(nèi)搜索【設(shè)置】程序并打開(kāi)。
步驟2:接下來(lái),依次找到【更新和安全】-【恢復(fù)】,在【高級(jí)啟動(dòng)】中點(diǎn)擊【立即重新啟動(dòng)】按鈕。
步驟3:這時(shí),您的系統(tǒng)將運(yùn)行至Windows 恢復(fù)環(huán)境當(dāng)中。接下來(lái)依次點(diǎn)擊【疑難解答】-【高級(jí)選項(xiàng)】-【啟動(dòng)修復(fù)】按鈕。然后計(jì)算機(jī)會(huì)診斷并修復(fù)損壞的注冊(cè)表內(nèi)容,您只需耐心等待該過(guò)程運(yùn)行完成即可。
系統(tǒng)文件檢查工具(SFC)可以在系統(tǒng)文件目錄中查找和修復(fù)損壞或丟失的系統(tǒng)文件,從而修復(fù)注冊(cè)表?yè)p壞的問(wèn)題。具體操作步驟如下:
步驟1:運(yùn)行計(jì)算機(jī),在左下角搜索框內(nèi)搜索【cmd】,并選擇【以管理員身份運(yùn)行】打開(kāi)命令提示符工具。若無(wú)法正常進(jìn)入系統(tǒng),您也可以啟用帶命令提示符的安全模式,再進(jìn)行下一步的操作。
步驟2:在打開(kāi)的命令提示符窗口里,輸入命令【SFC /scannow】并按下【Enter】鍵。接下來(lái)系統(tǒng)文件檢查器會(huì)進(jìn)行系統(tǒng)掃描,并修復(fù)有問(wèn)題的系統(tǒng)文件,比如損壞的注冊(cè)表。修復(fù)完成后,重啟電腦查看問(wèn)題是否已解決。若沒(méi)有解決,請(qǐng)繼續(xù)嘗試下面的方法。
DISM是一款微軟官方出品的映像工具。DISM命令通常用于檢查和修復(fù)系統(tǒng)映像文件,并且檢測(cè)和清理注冊(cè)表項(xiàng)目,下面我們就來(lái)學(xué)習(xí)如何使用DISM命令修復(fù)損壞的注冊(cè)表吧。具體操作步驟如下:
步驟1:運(yùn)行計(jì)算機(jī),在左下角搜索框內(nèi)搜索【cmd】,并選擇【以管理員身份運(yùn)行】打開(kāi)命令提示符工具。若無(wú)法正常進(jìn)入系統(tǒng),您也可以啟用帶命令提示符的安全模式,再進(jìn)行下一步的操作。
步驟2:在打開(kāi)的命令提示符窗口里,輸入命令【DISM / Online / Cleanup-Image / ScanHealth】并按下【Enter】。等待過(guò)程執(zhí)行完成再查看問(wèn)題是否已解決。
借助系統(tǒng)還原的方法在于將系統(tǒng)注冊(cè)表還原到注冊(cè)表未損壞前的還原點(diǎn),從而達(dá)到修復(fù)損壞注冊(cè)表的目的。具體操作步驟如下:
步驟1:運(yùn)行計(jì)算機(jī),在左下角搜索框內(nèi)搜索【控制面板】程序并打開(kāi)。
步驟2:接下來(lái),在控制面板界面里找到【恢復(fù)】-【開(kāi)始系統(tǒng)還原】選項(xiàng),然后點(diǎn)擊【下一步】。
步驟3:然后,選中一個(gè)發(fā)生錯(cuò)誤之前最近的還原點(diǎn)進(jìn)行還原,點(diǎn)擊【下一步】等待還原過(guò)程完成即可。
系統(tǒng)還原的前提是已經(jīng)創(chuàng)建了還原點(diǎn),如果沒(méi)有創(chuàng)建還原點(diǎn),還可以嘗試重置電腦,同時(shí)保存?zhèn)€人文件的方法。