C#使用RegNotifyChangeKeyValue監(jiān)聽注冊表更改的方法小結(jié)
養(yǎng)成一個好習(xí)慣,調(diào)用 Windows API 之前一定要先看文檔
RegNotifyChangeKeyValue 函數(shù) (winreg.h) - Win32 apps | Microsoft Learn
同步阻塞模式
RegNotifyChangeKeyValue
的最后一個參數(shù)傳遞false
,表示以同步的方式監(jiān)聽。
同步模式會阻塞調(diào)用線程,直到監(jiān)聽的目標(biāo)發(fā)生更改才會返回,如果在UI線程上調(diào)用,則會導(dǎo)致界面卡死,因此我們一般不會直接在主線程上同步監(jiān)聽,往往是創(chuàng)建一個新的線程來監(jiān)聽。
示例代碼因?yàn)槭强刂婆_程序,因此沒有創(chuàng)建新的線程。
RegistryKey hKey = Registry.CurrentUser.CreateSubKey("SOFTWARE\\1-RegMonitor"); string changeBefore = hKey.GetValue("TestValue").ToString(); Console.WriteLine($"TestValue的當(dāng)前值是:{changeBefore}, 時間:{DateTime.Now:HH:mm:ss}"); //此處創(chuàng)建一個任務(wù),5s之后修改TestValue的值為一個新的guid Task.Delay(5000).ContinueWith(t => { string newValue = Guid.NewGuid().ToString(); Console.WriteLine($"TestValue的值即將被改為:{newValue}, 時間:{DateTime.Now:HH:mm:ss}"); hKey.SetValue("TestValue", newValue); }); int ret = RegNotifyChangeKeyValue(hKey.Handle, false, RegNotifyFilter.ChangeLastSet, new SafeWaitHandle(IntPtr.Zero, true), false); if(ret != 0) { Console.WriteLine($"出錯了:{ret}"); return; } string currentValue = hKey.GetValue("TestValue").ToString(); Console.WriteLine($"TestValue的最新值是:{currentValue}, 時間:{DateTime.Now:HH:mm:ss}"); hKey.Close(); Console.ReadLine();
運(yùn)行結(jié)果:
異步模式
RegNotifyChangeKeyValue
的最后一個參數(shù)傳遞true
,表示以異步的方式監(jiān)聽。
異步模式的關(guān)鍵點(diǎn)是需要創(chuàng)建一個事件,然后RegNotifyChangeKeyValue
會立即返回,不會阻塞調(diào)用線程,然后需要在其他的線程中等待事件的觸發(fā)。
當(dāng)然也可以在RegNotifyChangeKeyValue
返回之后立即等待事件,這樣跟同步阻塞沒有什么區(qū)別,如果不是出于演示目的,則沒什么意義。
出于演示目的毫無意義的異步模式示例:
RegistryKey hKey = Registry.CurrentUser.CreateSubKey("SOFTWARE\\1-RegMonitor"); string changeBefore = hKey.GetValue("TestValue").ToString(); Console.WriteLine($"TestValue的當(dāng)前值是:{changeBefore}, 時間:{DateTime.Now:HH:mm:ss}"); //此處創(chuàng)建一個任務(wù),5s之后修改TestValue的值為一個新的guid Task.Delay(5000).ContinueWith(t => { string newValue = Guid.NewGuid().ToString(); Console.WriteLine($"TestValue的值即將被改為:{newValue}, 時間:{DateTime.Now:HH:mm:ss}"); hKey.SetValue("TestValue", newValue); }); ManualResetEvent manualResetEvent = new ManualResetEvent(false); int ret = RegNotifyChangeKeyValue(hKey.Handle, false, RegNotifyFilter.ChangeLastSet, manualResetEvent.SafeWaitHandle, true); if(ret != 0) { Console.WriteLine($"出錯了:{ret}"); return; } Console.WriteLine($"RegNotifyChangeKeyValue立即返回,時間:{DateTime.Now:HH:mm:ss}"); manualResetEvent.WaitOne(); string currentValue = hKey.GetValue("TestValue").ToString(); Console.WriteLine($"TestValue的最新值是:{currentValue}, 時間:{DateTime.Now:HH:mm:ss}"); hKey.Close(); manualResetEvent.Close(); Console.WriteLine("收工"); Console.ReadLine();
運(yùn)行結(jié)果:
正經(jīng)的代碼大概應(yīng)該這么寫:
演示代碼請忽略參數(shù)未判空,異常未處理等場景
class RegistryMonitor { private Thread m_thread = null; private string m_keyName; private RegNotifyFilter m_notifyFilter = RegNotifyFilter.ChangeLastSet; public event EventHandler RegistryChanged; public RegistryMonitor(string keyName, RegNotifyFilter notifyFilter) { this.m_keyName = keyName; this.m_notifyFilter = notifyFilter; this.m_thread = new Thread(ThreadAction); this.m_thread.IsBackground = true; } public void Start() { this.m_thread.Start(); } private void ThreadAction() { using(RegistryKey hKey = Registry.CurrentUser.CreateSubKey(this.m_keyName)) { using(ManualResetEvent waitHandle = new ManualResetEvent(false)) { int ret = RegNotifyChangeKeyValue(hKey.Handle, false, this.m_notifyFilter, waitHandle.SafeWaitHandle, true); waitHandle.WaitOne(); this.RegistryChanged?.Invoke(this, EventArgs.Empty); } } } } static void Main(string[] args) { string keyName = "SOFTWARE\\1-RegMonitor"; RegistryKey hKey = Registry.CurrentUser.CreateSubKey(keyName); string changeBefore = hKey.GetValue("TestValue").ToString(); Console.WriteLine($"TestValue的當(dāng)前值是:{changeBefore}, 時間:{DateTime.Now:HH:mm:ss}"); //此處創(chuàng)建一個任務(wù),5s之后修改TestValue的值為一個新的guid Task.Delay(5000).ContinueWith(t => { string newValue = Guid.NewGuid().ToString(); Console.WriteLine($"TestValue的值即將被改為:{newValue}, 時間:{DateTime.Now:HH:mm:ss}"); hKey.SetValue("TestValue", newValue); }); RegistryMonitor monitor = new RegistryMonitor(keyName, RegNotifyFilter.ChangeLastSet); monitor.RegistryChanged += (sender, e) => { Console.WriteLine($"{keyName}的值發(fā)生了改變"); string currentValue = hKey.GetValue("TestValue").ToString(); Console.WriteLine($"TestValue的最新值是:{currentValue}, 時間:{DateTime.Now:HH:mm:ss}"); hKey.Close(); }; monitor.Start(); Console.WriteLine("收工"); Console.ReadLine(); }
運(yùn)行結(jié)果:
那么問題來了:
- 上面監(jiān)聽一個路徑就需要創(chuàng)建一個線程,如果要監(jiān)聽多個路徑,就需要創(chuàng)建多個線程,且他們什么事都不干,就在那等,這不太科學(xué)。
- 經(jīng)常寫C#的都知道,一般不建議代碼中直接創(chuàng)建
Thread
。 - 改成線程池或者
Task
行不行?如果在線程池或者Task
里面調(diào)用WaitOne
進(jìn)行阻塞,那也是不行的。
接下來 ,我們嘗試改造一下
基于線程池的異步模式
調(diào)用線程池的
RegisterWaitForSingleObject
,給一個事件注冊一個回調(diào),當(dāng)事件觸發(fā)時,則執(zhí)行指定的回調(diào)函數(shù),參考ThreadPool.RegisterWaitForSingleObject 方法 (System.Threading) | Microsoft Learn
代碼實(shí)例如下:
class RegistryMonitor { private string m_keyName; private RegNotifyFilter m_notifyFilter = RegNotifyFilter.ChangeLastSet; private RegisteredWaitHandle m_registered = null; private RegistryKey m_key = null; private ManualResetEvent m_waitHandle = null; public event EventHandler RegistryChanged; public RegistryMonitor(string keyName, RegNotifyFilter notifyFilter) { this.m_keyName = keyName; this.m_notifyFilter = notifyFilter; } public void Start() { this.m_key = Registry.CurrentUser.CreateSubKey(this.m_keyName); this.m_waitHandle = new ManualResetEvent(false); int ret = RegNotifyChangeKeyValue(this.m_key.Handle, false, this.m_notifyFilter | RegNotifyFilter.ThreadAgnostic, this.m_waitHandle.SafeWaitHandle, true); this.m_registered = ThreadPool.RegisterWaitForSingleObject(this.m_waitHandle, Callback, null, Timeout.Infinite, true); } private void Callback(object state, bool timedOut) { this.m_registered.Unregister(this.m_waitHandle); this.m_waitHandle.Close(); this.m_key.Close(); this.RegistryChanged?.Invoke(this, EventArgs.Empty); } } static void Main(string[] args) { for(int i = 1; i <= 50; i++) { string keyName = $"SOFTWARE\\1-RegMonitor\\{i}"; RegistryKey hKey = Registry.CurrentUser.CreateSubKey(keyName); hKey.SetValue("TestValue", Guid.NewGuid().ToString()); string changeBefore = hKey.GetValue("TestValue").ToString(); Console.WriteLine($"{keyName} TestValue的當(dāng)前值是:{changeBefore}, 時間:{DateTime.Now:HH:mm:ss}"); RegistryMonitor monitor = new RegistryMonitor(keyName, RegNotifyFilter.ChangeLastSet); monitor.RegistryChanged += (sender, e) => { Console.WriteLine($"{keyName}的值發(fā)生了改變"); string currentValue = hKey.GetValue("TestValue").ToString(); Console.WriteLine($"{keyName} TestValue的最新值是:{currentValue}, 時間:{DateTime.Now:HH:mm:ss}"); hKey.Close(); }; monitor.Start(); Console.WriteLine($"{keyName}監(jiān)聽中..."); } Console.WriteLine("收工"); Console.ReadLine(); }
運(yùn)行結(jié)果:
可以看到,創(chuàng)建50個監(jiān)聽,而進(jìn)程的總線程數(shù)只有7個。因此使用線程池是最佳方案。
注意事項(xiàng)
- 官方文檔有說明,調(diào)用
RegNotifyChangeKeyValue
需要再持久化的線程中,如果不能保證調(diào)用線程持久化(如在線程池中調(diào)用),則可以加上REG_NOTIFY_THREAD_AGNOSTIC
標(biāo)識 - 示例中的監(jiān)聽都是一次性的,重復(fù)監(jiān)聽只需要在事件觸發(fā)后再次執(zhí)行
RegNotifyChangeKeyValue
的流程即可
基礎(chǔ)代碼
/// <summary> /// 指示應(yīng)報告的更改 /// </summary> [Flags] enum RegNotifyFilter { /// <summary> /// 通知調(diào)用方是添加還是刪除了子項(xiàng) /// </summary> ChangeName = 0x00000001, /// <summary> /// 向調(diào)用方通知項(xiàng)屬性(例如安全描述符信息)的更改 /// </summary> ChangeAttributes = 0x00000002, /// <summary> /// 向調(diào)用方通知項(xiàng)值的更改。 這包括添加或刪除值,或更改現(xiàn)有值 /// </summary> ChangeLastSet = 0x00000004, /// <summary> /// 向調(diào)用方通知項(xiàng)的安全描述符的更改 /// </summary> ChangeSecurity = 0x00000008, /// <summary> /// 指示注冊的生存期不得綁定到發(fā)出 RegNotifyChangeKeyValue 調(diào)用的線程的生存期。<b>注意</b> 此標(biāo)志值僅在 Windows 8 及更高版本中受支持。 /// </summary> ThreadAgnostic = 0x10000000 } /// <summary> /// 通知調(diào)用方對指定注冊表項(xiàng)的屬性或內(nèi)容的更改。 /// </summary> /// <param name="hKey">打開的注冊表項(xiàng)的句柄。密鑰必須已使用KEY_NOTIFY訪問權(quán)限打開。</param> /// <param name="bWatchSubtree">如果此參數(shù)為 TRUE,則函數(shù)將報告指定鍵及其子項(xiàng)中的更改。 如果參數(shù)為 FALSE,則函數(shù)僅報告指定鍵中的更改。</param> /// <param name="dwNotifyFilter"> /// 一個值,該值指示應(yīng)報告的更改。 此參數(shù)可使用以下一個或多個值。<br/> /// REG_NOTIFY_CHANGE_NAME 0x00000001L 通知調(diào)用方是添加還是刪除了子項(xiàng)。<br/> /// REG_NOTIFY_CHANGE_ATTRIBUTES 0x00000002L 向調(diào)用方通知項(xiàng)屬性(例如安全描述符信息)的更改。<br/> /// REG_NOTIFY_CHANGE_LAST_SET 0x00000004L 向調(diào)用方通知項(xiàng)值的更改。 這包括添加或刪除值,或更改現(xiàn)有值。<br/> /// REG_NOTIFY_CHANGE_SECURITY 0x00000008L 向調(diào)用方通知項(xiàng)的安全描述符的更改。<br/> /// REG_NOTIFY_THREAD_AGNOSTIC 0x10000000L 指示注冊的生存期不得綁定到發(fā)出 RegNotifyChangeKeyValue 調(diào)用的線程的生存期。<b>注意</b> 此標(biāo)志值僅在 Windows 8 及更高版本中受支持。 /// </param> /// <param name="hEvent">事件的句柄。 如果 fAsynchronous 參數(shù)為 TRUE,則函數(shù)將立即返回 ,并通過發(fā)出此事件信號來報告更改。 如果 fAsynchronous 為 FALSE,則忽略 hEvent 。</param> /// <param name="fAsynchronous"> /// 如果此參數(shù)為 TRUE,則函數(shù)將立即返回并通過向指定事件發(fā)出信號來報告更改。 如果此參數(shù)為 FALSE,則函數(shù)在發(fā)生更改之前不會返回 。<br/> /// 如果 hEvent 未指定有效的事件, 則 fAsynchronous 參數(shù)不能為 TRUE。 /// </param> /// <returns></returns> [DllImport("Advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int RegNotifyChangeKeyValue(SafeHandle hKey, bool bWatchSubtree, RegNotifyFilter dwNotifyFilter, SafeHandle hEvent, bool fAsynchronous);
到此這篇關(guān)于C#使用RegNotifyChangeKeyValue監(jiān)聽注冊表更改的幾種方式的文章就介紹到這了,更多相關(guān)C# RegNotifyChangeKeyValue監(jiān)聽注冊表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用.NET創(chuàng)建Windows服務(wù)的方法
用.NET創(chuàng)建Windows服務(wù)的方法...2007-03-03登錄驗(yàn)證全局控制的幾種方式總結(jié)(session)
在登陸驗(yàn)證或者其他需要用到session全局變量的時候,歸結(jié)起來,主要有以下三種較方便的實(shí)現(xiàn)方式。(其中個人較喜歡使用第一種實(shí)現(xiàn)方法)2014-01-01C# Access數(shù)據(jù)庫增刪查改的簡單方法
這篇文章主要介紹了C# Access數(shù)據(jù)庫增刪查改的簡單方法,有需要的朋友可以參考一下2014-01-01Unity UGUI實(shí)現(xiàn)簡單拖拽圖片功能
這篇文章主要為大家詳細(xì)介紹了Unity UGUI實(shí)現(xiàn)簡單拖拽圖片功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-06-06使用C# CefSharp Python采集某網(wǎng)站簡歷并且自動發(fā)送邀請短信的方法
這篇文章主要給大家介紹了關(guān)于如何使用C# CefSharp Python采集某網(wǎng)站簡歷并且自動發(fā)送邀請短信的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧2019-03-03C#操作SQLite數(shù)據(jù)庫方法小結(jié)(創(chuàng)建,連接,插入,查詢,刪除等)
這篇文章主要介紹了C#操作SQLite數(shù)據(jù)庫方法,包括針對SQLite數(shù)據(jù)庫的創(chuàng)建,連接,插入,查詢,刪除等操作,并提供了一個SQLite的封裝類,需要的朋友可以參考下2016-07-07C#使用Clipboard類實(shí)現(xiàn)剪貼板功能
這篇文章介紹了C#使用Clipboard類實(shí)現(xiàn)剪貼板功能的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06C#實(shí)現(xiàn)Zip壓縮目錄中所有文件的方法
這篇文章主要介紹了C#實(shí)現(xiàn)Zip壓縮目錄中所有文件的方法,涉及C#針對文件的讀寫與zip壓縮相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07