You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 6迁移中监听Windows会话切换事件的问题求助

.NET 6 Windows服务监听会话切换事件的问题解决

SystemEvents在.NET Core/5+的可用性

SystemEvents.SessionSwitch在.NET Core/5+中是可用的,但存在关键限制:它依赖Windows UI消息循环,且仅在创建订阅的STA(单线程单元)线程上触发事件。Windows服务默认运行在会话0的MTA线程环境中,没有消息循环,直接绑定事件自然无法收到回调。

你遗漏的核心点

  1. 线程模型不匹配:SystemEvents要求订阅事件的线程必须是STA模式,且该线程需维持消息循环(如Application.Run())才能接收系统事件通知。
  2. 会话隔离:Windows服务默认运行在会话0,用户会话切换事件属于用户会话(通常为会话1及以上),SystemEvents在会话0中无法直接捕获跨会话的切换事件。

可行解决方案

方案一:使用Windows原生会话通知API(推荐)

这是最可靠的服务端监听方案,直接调用Windows系统API,不依赖UI消息循环。

  1. 定义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);
}
  1. 自定义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();
    }
}
  1. 注册服务:
    若使用微软官方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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 13:55:18