.NET 6迁移中监听Windows会话切换事件的问题求助
.NET 6 Windows服务监听会话切换事件的问题解决
SystemEvents在.NET Core/5+的可用性
SystemEvents.SessionSwitch在.NET Core/5+中是可用的,但存在关键限制:它依赖Windows UI消息循环,且仅在创建订阅的STA(单线程单元)线程上触发事件。Windows服务默认运行在会话0的MTA线程环境中,没有消息循环,直接绑定事件自然无法收到回调。
你遗漏的核心点
- 线程模型不匹配:
SystemEvents要求订阅事件的线程必须是STA模式,且该线程需维持消息循环(如Application.Run())才能接收系统事件通知。 - 会话隔离:Windows服务默认运行在会话0,用户会话切换事件属于用户会话(通常为会话1及以上),
SystemEvents在会话0中无法直接捕获跨会话的切换事件。
可行解决方案
方案一:使用Windows原生会话通知API(推荐)
这是最可靠的服务端监听方案,直接调用Windows系统API,不依赖UI消息循环。
- 定义Windows API导入:
using System.Runtime.InteropServices; public static class WtsApi { private const int NOTIFY_FOR_ALL_SESSIONS = 1; [DllImport("Wtsapi32.dll")] public static extern bool WTSRegisterSessionNotification(IntPtr hWnd, int dwFlags); [DllImport("Wtsapi32.dll")] public static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd); }
- 自定义ServiceBase子类实现监听:
.NET 6的Windows服务基于ServiceBase,可重写OnSessionChange方法处理会话事件:
public class SessionMonitorService : ServiceBase { private readonly ILogger<SessionMonitorService> _logger; private readonly IThirdPartyMessageSender _messageSender; public SessionMonitorService(ILogger<SessionMonitorService> logger, IThirdPartyMessageSender messageSender) { _logger = logger; _messageSender = messageSender; } protected override void OnStart(string[] args) { WtsApi.WTSRegisterSessionNotification(this.ServiceHandle, WtsApi.NOTIFY_FOR_ALL_SESSIONS); base.OnStart(args); } protected override void OnSessionChange(SessionChangeDescription changeDescription) { base.OnSessionChange(changeDescription); switch (changeDescription.Reason) { case SessionChangeReason.SessionLogon: _logger.LogInformation("用户登入会话 {SessionId}", changeDescription.SessionId); _messageSender.SendLogonMessage(changeDescription.SessionId); break; case SessionChangeReason.SessionLogoff: _logger.LogInformation("用户登出会话 {SessionId}", changeDescription.SessionId); _messageSender.SendLogoffMessage(changeDescription.SessionId); break; } } protected override void OnStop() { WtsApi.WTSUnRegisterSessionNotification(this.ServiceHandle); base.OnStop(); } }
- 注册服务:
若使用微软官方Windows服务托管:
builder.Services.AddHostedService<SessionMonitorService>(); builder.Services.AddWindowsService(options => { options.ServiceName = "YourAppService"; });
方案二:修复SystemEvents.SessionSwitch的使用
若坚持使用SystemEvents,需手动创建STA线程并维持消息循环:
public class SessionEventHostedService : IHostedService { private readonly ILogger<SessionEventHostedService> _logger; private readonly IThirdPartyMessageSender _messageSender; private Thread _eventThread; private ManualResetEvent _shutdownEvent; public SessionEventHostedService(ILogger<SessionEventHostedService> logger, IThirdPartyMessageSender messageSender) { _logger = logger; _messageSender = messageSender; _shutdownEvent = new ManualResetEvent(false); } public Task StartAsync(CancellationToken cancellationToken) { _eventThread = new Thread(RunMessageLoop) { IsBackground = true, Name = "SessionEventThread", ApartmentState = ApartmentState.STA // 必须设置为STA模式 }; _eventThread.Start(); return Task.CompletedTask; } private void RunMessageLoop() { SystemEvents.SessionSwitch += OnSessionSwitch; while (!_shutdownEvent.WaitOne(0)) { Application.DoEvents(); // 维持消息循环 Thread.Sleep(100); } SystemEvents.SessionSwitch -= OnSessionSwitch; } private void OnSessionSwitch(object sender, SessionSwitchEventArgs e) { switch (e.Reason) { case SessionSwitchReason.SessionLogon: _logger.LogInformation("用户登入"); _messageSender.SendLogonMessage(); break; case SessionSwitchReason.SessionLogoff: _logger.LogInformation("用户登出"); _messageSender.SendLogoffMessage(); break; } } public Task StopAsync(CancellationToken cancellationToken) { _shutdownEvent.Set(); _eventThread.Join(); return Task.CompletedTask; } }
注意:此方案需引用System.Windows.Forms NuGet包,稳定性不如原生API方案,仅推荐特殊场景使用。
Topshelf集成
若必须沿用Topshelf,可在DI容器构建完成后,将监听服务注入到Topshelf的主服务中:
HostFactory.Run(x => { x.Service<YourMainService>(s => { s.ConstructUsing(name => new YourMainService( Program.ServiceProvider.GetRequiredService<ILogger<YourMainService>>(), Program.ServiceProvider.GetRequiredService<SessionMonitorService>())); s.WhenStarted(service => service.Start()); s.WhenStopped(service => service.Stop()); }); x.RunAsLocalSystem(); x.SetServiceName("YourTopshelfService"); });
内容的提问来源于stack exchange,提问作者Ventsislav Petrov
相关产品推荐
相关产品推荐

