翼度科技»论坛 编程开发 .net 查看内容

C#使用RegNotifyChangeKeyValue监听注册表更改的几种方式

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
养成一个好习惯,调用 Windows API 之前一定要先看文档
RegNotifyChangeKeyValue 函数 (winreg.h) - Win32 apps | Microsoft Learn
同步阻塞模式

RegNotifyChangeKeyValue的最后一个参数传递false,表示以同步的方式监听。
同步模式会阻塞调用线程,直到监听的目标发生更改才会返回,如果在UI线程上调用,则会导致界面卡死,因此我们一般不会直接在主线程上同步监听,往往是创建一个新的线程来监听。
示例代码因为是控制台程序,因此没有创建新的线程。
  1. RegistryKey hKey = Registry.CurrentUser.CreateSubKey("SOFTWARE\\1-RegMonitor");
  2. string changeBefore = hKey.GetValue("TestValue").ToString();
  3. Console.WriteLine($"TestValue的当前值是:{changeBefore}, 时间:{DateTime.Now:HH:mm:ss}");
  4. //此处创建一个任务,5s之后修改TestValue的值为一个新的guid
  5. Task.Delay(5000).ContinueWith(t =>
  6. {
  7.     string newValue = Guid.NewGuid().ToString();
  8.     Console.WriteLine($"TestValue的值即将被改为:{newValue}, 时间:{DateTime.Now:HH:mm:ss}");
  9.     hKey.SetValue("TestValue", newValue);
  10. });
  11. int ret = RegNotifyChangeKeyValue(hKey.Handle, false, RegNotifyFilter.ChangeLastSet, new SafeWaitHandle(IntPtr.Zero, true), false);
  12. if(ret != 0)
  13. {
  14.     Console.WriteLine($"出错了:{ret}");
  15.     return;
  16. }
  17. string currentValue = hKey.GetValue("TestValue").ToString();
  18. Console.WriteLine($"TestValue的最新值是:{currentValue}, 时间:{DateTime.Now:HH:mm:ss}");
  19. hKey.Close();
  20. Console.ReadLine();
复制代码
运行结果:

异步模式

RegNotifyChangeKeyValue的最后一个参数传递true,表示以异步的方式监听。
异步模式的关键点是需要创建一个事件,然后RegNotifyChangeKeyValue会立即返回,不会阻塞调用线程,然后需要在其他的线程中等待事件的触发。
当然也可以在RegNotifyChangeKeyValue返回之后立即等待事件,这样跟同步阻塞没有什么区别,如果不是出于演示目的,则没什么意义。
出于演示目的毫无意义的异步模式示例:
  1. RegistryKey hKey = Registry.CurrentUser.CreateSubKey("SOFTWARE\\1-RegMonitor");
  2. string changeBefore = hKey.GetValue("TestValue").ToString();
  3. Console.WriteLine($"TestValue的当前值是:{changeBefore}, 时间:{DateTime.Now:HH:mm:ss}");
  4. //此处创建一个任务,5s之后修改TestValue的值为一个新的guid
  5. Task.Delay(5000).ContinueWith(t =>
  6. {
  7.     string newValue = Guid.NewGuid().ToString();
  8.     Console.WriteLine($"TestValue的值即将被改为:{newValue}, 时间:{DateTime.Now:HH:mm:ss}");
  9.     hKey.SetValue("TestValue", newValue);
  10. });
  11. ManualResetEvent manualResetEvent = new ManualResetEvent(false);
  12. int ret = RegNotifyChangeKeyValue(hKey.Handle, false, RegNotifyFilter.ChangeLastSet, manualResetEvent.SafeWaitHandle, true);
  13. if(ret != 0)
  14. {
  15.     Console.WriteLine($"出错了:{ret}");
  16.     return;
  17. }
  18. Console.WriteLine($"RegNotifyChangeKeyValue立即返回,时间:{DateTime.Now:HH:mm:ss}");
  19. manualResetEvent.WaitOne();
  20. string currentValue = hKey.GetValue("TestValue").ToString();
  21. Console.WriteLine($"TestValue的最新值是:{currentValue}, 时间:{DateTime.Now:HH:mm:ss}");
  22. hKey.Close();
  23. manualResetEvent.Close();
  24. Console.WriteLine("收工");
  25. Console.ReadLine();
复制代码
运行结果:

正经的代码大概应该这么写:
演示代码请忽略参数未判空,异常未处理等场景
  1. class RegistryMonitor
  2. {
  3.     private Thread m_thread = null;
  4.     private string m_keyName;
  5.     private RegNotifyFilter m_notifyFilter = RegNotifyFilter.ChangeLastSet;
  6.     public event EventHandler RegistryChanged;
  7.     public RegistryMonitor(string keyName, RegNotifyFilter notifyFilter)
  8.     {
  9.         this.m_keyName = keyName;
  10.         this.m_notifyFilter = notifyFilter;
  11.         this.m_thread = new Thread(ThreadAction);
  12.         this.m_thread.IsBackground = true;
  13.     }
  14.     public void Start()
  15.     {
  16.         this.m_thread.Start();
  17.     }
  18.     private void ThreadAction()
  19.     {
  20.         using(RegistryKey hKey = Registry.CurrentUser.CreateSubKey(this.m_keyName))
  21.         {
  22.             using(ManualResetEvent waitHandle = new ManualResetEvent(false))
  23.             {
  24.                 int ret = RegNotifyChangeKeyValue(hKey.Handle, false, this.m_notifyFilter, waitHandle.SafeWaitHandle, true);
  25.                 waitHandle.WaitOne();
  26.                 this.RegistryChanged?.Invoke(this, EventArgs.Empty);
  27.             }
  28.         }
  29.     }
  30. }
  31. static void Main(string[] args)
  32. {
  33.     string keyName = "SOFTWARE\\1-RegMonitor";
  34.     RegistryKey hKey = Registry.CurrentUser.CreateSubKey(keyName);
  35.     string changeBefore = hKey.GetValue("TestValue").ToString();
  36.     Console.WriteLine($"TestValue的当前值是:{changeBefore}, 时间:{DateTime.Now:HH:mm:ss}");
  37.     //此处创建一个任务,5s之后修改TestValue的值为一个新的guid
  38.     Task.Delay(5000).ContinueWith(t =>
  39.     {
  40.         string newValue = Guid.NewGuid().ToString();
  41.         Console.WriteLine($"TestValue的值即将被改为:{newValue}, 时间:{DateTime.Now:HH:mm:ss}");
  42.         hKey.SetValue("TestValue", newValue);
  43.     });
  44.     RegistryMonitor monitor = new RegistryMonitor(keyName, RegNotifyFilter.ChangeLastSet);
  45.     monitor.RegistryChanged += (sender, e) =>
  46.     {
  47.         Console.WriteLine($"{keyName}的值发生了改变");
  48.         string currentValue = hKey.GetValue("TestValue").ToString();
  49.         Console.WriteLine($"TestValue的最新值是:{currentValue}, 时间:{DateTime.Now:HH:mm:ss}");
  50.         hKey.Close();
  51.     };
  52.     monitor.Start();
  53.     Console.WriteLine("收工");
  54.     Console.ReadLine();
  55. }
复制代码
运行结果:

那么问题来了:

  • 上面监听一个路径就需要创建一个线程,如果要监听多个路径,就需要创建多个线程,且他们什么事都不干,就在那等,这不太科学。
  • 经常写C#的都知道,一般不建议代码中直接创建Thread。
  • 改成线程池或者Task行不行?如果在线程池或者Task里面调用WaitOne进行阻塞,那也是不行的。
接下来 ,我们尝试改造一下
基于线程池的异步模式

调用线程池的RegisterWaitForSingleObject,给一个事件注册一个回调,当事件触发时,则执行指定的回调函数,参考ThreadPool.RegisterWaitForSingleObject 方法 (System.Threading) | Microsoft Learn
代码实例如下:
  1. class RegistryMonitor
  2. {
  3.     private string m_keyName;
  4.     private RegNotifyFilter m_notifyFilter = RegNotifyFilter.ChangeLastSet;
  5.     private RegisteredWaitHandle m_registered = null;
  6.     private RegistryKey m_key = null;
  7.     private ManualResetEvent m_waitHandle = null;
  8.     public event EventHandler RegistryChanged;
  9.     public RegistryMonitor(string keyName, RegNotifyFilter notifyFilter)
  10.     {
  11.         this.m_keyName = keyName;
  12.         this.m_notifyFilter = notifyFilter;
  13.     }
  14.     public void Start()
  15.     {
  16.         this.m_key = Registry.CurrentUser.CreateSubKey(this.m_keyName);
  17.         this.m_waitHandle = new ManualResetEvent(false);
  18.         int ret = RegNotifyChangeKeyValue(this.m_key.Handle, false, this.m_notifyFilter | RegNotifyFilter.ThreadAgnostic, this.m_waitHandle.SafeWaitHandle, true);
  19.         this.m_registered = ThreadPool.RegisterWaitForSingleObject(this.m_waitHandle, Callback, null, Timeout.Infinite, true);
  20.     }
  21.     private void Callback(object state, bool timedOut)
  22.     {
  23.         this.m_registered.Unregister(this.m_waitHandle);
  24.         this.m_waitHandle.Close();
  25.         this.m_key.Close();
  26.         this.RegistryChanged?.Invoke(this, EventArgs.Empty);
  27.     }
  28. }
  29. static void Main(string[] args)
  30. {
  31.     for(int i = 1; i <= 50; i++)
  32.     {
  33.         string keyName = $"SOFTWARE\\1-RegMonitor\\{i}";
  34.         RegistryKey hKey = Registry.CurrentUser.CreateSubKey(keyName);
  35.         hKey.SetValue("TestValue", Guid.NewGuid().ToString());
  36.         string changeBefore = hKey.GetValue("TestValue").ToString();
  37.         Console.WriteLine($"{keyName} TestValue的当前值是:{changeBefore}, 时间:{DateTime.Now:HH:mm:ss}");
  38.         RegistryMonitor monitor = new RegistryMonitor(keyName, RegNotifyFilter.ChangeLastSet);
  39.         monitor.RegistryChanged += (sender, e) =>
  40.         {
  41.             Console.WriteLine($"{keyName}的值发生了改变");
  42.             string currentValue = hKey.GetValue("TestValue").ToString();
  43.             Console.WriteLine($"{keyName} TestValue的最新值是:{currentValue}, 时间:{DateTime.Now:HH:mm:ss}");
  44.             hKey.Close();
  45.         };
  46.         monitor.Start();
  47.         Console.WriteLine($"{keyName}监听中...");
  48.     }
  49.     Console.WriteLine("收工");
  50.     Console.ReadLine();
  51. }
复制代码
运行结果:

可以看到,创建50个监听,而进程的总线程数只有7个。因此使用线程池是最佳方案。
注意事项


  • 官方文档有说明,调用RegNotifyChangeKeyValue需要再持久化的线程中,如果不能保证调用线程持久化(如在线程池中调用),则可以加上REG_NOTIFY_THREAD_AGNOSTIC标识
  • 示例中的监听都是一次性的,重复监听只需要在事件触发后再次执行RegNotifyChangeKeyValue的流程即可
基础代码
  1. /// <summary>
  2. /// 指示应报告的更改
  3. /// </summary>
  4. [Flags]
  5. enum RegNotifyFilter
  6. {
  7.     /// <summary>
  8.     /// 通知调用方是添加还是删除了子项
  9.     /// </summary>
  10.     ChangeName = 0x00000001,
  11.     /// <summary>
  12.     /// 向调用方通知项属性(例如安全描述符信息)的更改
  13.     /// </summary>
  14.     ChangeAttributes = 0x00000002,
  15.     /// <summary>
  16.     /// 向调用方通知项值的更改。 这包括添加或删除值,或更改现有值
  17.     /// </summary>
  18.     ChangeLastSet = 0x00000004,
  19.     /// <summary>
  20.     /// 向调用方通知项的安全描述符的更改
  21.     /// </summary>
  22.     ChangeSecurity = 0x00000008,
  23.     /// <summary>
  24.     /// 指示注册的生存期不得绑定到发出 RegNotifyChangeKeyValue 调用的线程的生存期。<b>注意</b> 此标志值仅在 Windows 8 及更高版本中受支持。
  25.     /// </summary>
  26.     ThreadAgnostic = 0x10000000
  27. }
  28. /// <summary>
  29. /// 通知调用方对指定注册表项的属性或内容的更改。
  30. /// </summary>
  31. /// <param name="hKey">打开的注册表项的句柄。密钥必须已使用KEY_NOTIFY访问权限打开。</param>
  32. /// <param name="bWatchSubtree">如果此参数为 TRUE,则函数将报告指定键及其子项中的更改。 如果参数为 FALSE,则函数仅报告指定键中的更改。</param>
  33. /// <param name="dwNotifyFilter">
  34. /// 一个值,该值指示应报告的更改。 此参数可使用以下一个或多个值。<br/>
  35. /// REG_NOTIFY_CHANGE_NAME 0x00000001L 通知调用方是添加还是删除了子项。<br/>
  36. /// REG_NOTIFY_CHANGE_ATTRIBUTES 0x00000002L 向调用方通知项属性(例如安全描述符信息)的更改。<br/>
  37. /// REG_NOTIFY_CHANGE_LAST_SET 0x00000004L 向调用方通知项值的更改。 这包括添加或删除值,或更改现有值。<br/>
  38. /// REG_NOTIFY_CHANGE_SECURITY 0x00000008L 向调用方通知项的安全描述符的更改。<br/>
  39. /// REG_NOTIFY_THREAD_AGNOSTIC 0x10000000L 指示注册的生存期不得绑定到发出 RegNotifyChangeKeyValue 调用的线程的生存期。<b>注意</b> 此标志值仅在 Windows 8 及更高版本中受支持。
  40. /// </param>
  41. /// <param name="hEvent">事件的句柄。 如果 fAsynchronous 参数为 TRUE,则函数将立即返回 ,并通过发出此事件信号来报告更改。 如果 fAsynchronous 为 FALSE,则忽略 hEvent 。</param>
  42. /// <param name="fAsynchronous">
  43. /// 如果此参数为 TRUE,则函数将立即返回并通过向指定事件发出信号来报告更改。 如果此参数为 FALSE,则函数在发生更改之前不会返回 。<br/>
  44. /// 如果 hEvent 未指定有效的事件, 则 fAsynchronous 参数不能为 TRUE。
  45. /// </param>
  46. /// <returns></returns>
  47. [DllImport("Advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  48. private static extern int RegNotifyChangeKeyValue(SafeHandle hKey, bool bWatchSubtree, RegNotifyFilter dwNotifyFilter, SafeHandle hEvent, bool fAsynchronous);
复制代码
来源:https://www.cnblogs.com/xyycare/p/18226626/csharp-reg-notify
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具