C# Windows服务是否需要确保可以在不同的线程上处理命令?

C# Windows服务是否需要确保可以在不同的线程上处理命令?,c#,windows-services,service-control-manager,C#,Windows Services,Service Control Manager,当通过服务控制管理器运行时,Windows服务是否需要假定可以在不同的线程上调用命令处理方法(OnStart、OnStop等),而无需确保(例如)在方法之间可以看到对成员的分配 public class MyService : ServiceBase { private Object _foo; protected override void OnStart(string[] args) { _foo = new Object(); }

当通过服务控制管理器运行时,Windows服务是否需要假定可以在不同的线程上调用命令处理方法(OnStart、OnStop等),而无需确保(例如)在方法之间可以看到对成员的分配

public class MyService : ServiceBase {

    private Object _foo;    

    protected override void OnStart(string[] args) {
        _foo = new Object();
    }

    protected override void OnStop() {
        if (_foo == null) {
            throw new Exception("Assignment not visible"); // Can this happen?
        }

        _foo = null;
    }

}
我无法保证不会抛出示例中的异常,但我找到的所有示例(包括)似乎都假设,例如,OnStart()中变量的赋值在OnStop()中始终可见


如果SCM没有做出这样的保证,我知道如何确保分配是可见的(例如,通过在服务中的所有读/写操作周围添加锁)。我感兴趣的是这些措施是否必要。

你给出的例子确实发生了,它只是非常不可能的例子:

_foo = new VeryLongAndTimeConsummingTask();
[编辑]:如注释中所述,SCM在OnStart完成之前阻止OnStop运行。 这个评论来自我可能的坏习惯,将启动和停止包装公开


如果在新的结束之前调用stop事件,则可能发生_foo为null的情况


而且_foo可以在代码中的加法器位置释放,因此最好先检查。

从某种意义上说,SCM不能保证不会抛出您概述的异常。当然,它不控制服务对私人成员的操纵-例如,如果附加服务代码影响
\u foo

已经这样说了,考虑下面的场景来理解为什么你的具体问题的答案显然是否定的:

1) 通过以下更改构建您的服务,以演示:

    public partial class MyService : ServiceBase
    {
        private Object _foo;
        private const string _logName = "MyService Log.txt"; // to support added logging

        public MyService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            // demonstrative logging
            var threadId = Thread.CurrentThread.ManagedThreadId;
            using (var log = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + _logName, true))
            {
                log.WriteLine("{0}:  In OnStart(string[]) on thread ID {1}.  Sleeping for 10 seconds...", DateTime.Now, threadId);
            }

            // Sleep before initializing _foo to allow calling OnStop before OnStart completes unless the SCM synchronizes calls to the methods.
            Thread.Sleep(10000);

            _foo = new Object();
        }

        protected override void OnStop()
        {
            // demonstrative logging added
            var threadId = Thread.CurrentThread.ManagedThreadId;
            using (var log = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + _logName, true))
            {
                log.WriteLine("{0}:  In OnStop() on thread ID {1}.", DateTime.Now, threadId);
            }

            if (_foo == null)
            {
                // demonstrative logging added
                using (var log = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + _logName, true))
                {
                    log.WriteLine("{0}:  _foo == null", DateTime.Now);
                }

                throw new Exception("Assignment not visible"); // Can this happen?
            }

            _foo = null;
        }
    }
2) 打开命令shell

3) 打开另一个命令shell

4) 在第一个命令shell中,如果尚未安装服务(使用
sc create
),请安装该服务,然后启动它(使用
net start
)。你应该看到:

MyService服务正在启动

在SCM等待10秒睡眠以启动服务时,应逐个添加尾随点

5) 在第二个命令shell中,尝试在10秒之前停止服务(使用
net stop
)。你应该看到:

服务正在启动或停止。请稍后再试。

因此,启动服务显然是一个阻塞操作,必须在停止服务之前完成

6) 10秒后检查第一个命令shell。你应该看到:

MyService服务已成功启动

7) 返回第二个命令shell并再次尝试停止服务。你应该看到:

MyService服务正在停止

MyService服务已成功停止

8) 查看生成的日志-例如:

2013年10月22日上午7:28:55:在线程ID为4的OnStart(字符串[])中。睡10秒钟

2013年10月22日上午7:29:17:在线程ID为5的OnStop()中

我认为使用两个命令shell快速启动和停止服务更容易;但是这个例子同样适用于一个命令shell

最后,你可能会发现Mitchell Taylor(CoolDadTx)对我的回答很有趣:

SCM使用的线程模型没有正式的AFAIK文档。已知的是,每个服务都在其自己的线程上被调用。但是,SCM可能会也可能不会使用线程池跨服务重用线程。当调用服务(启动、停止、自定义命令等)时,它将执行其任务并快速返回。这需要多长时间有一个很强的限制。除了快速返回之外,还需要将请求推送到辅助线程进行处理。SCM本身在一个单独的线程上运行,因此,如果服务响应时间过长,SCM会将其视为挂起。这里讨论了这一点:

更新:


特别注意Mitchell Taylor引用的文章所链接的MSDN文章。它包含一个状态图,非常清晰且权威地记录了定义的服务状态转换,并与我上面概述的内容保持一致。它还解释了与状态图相关的SCM如何有时不发送服务控制请求,以确保仅定义状态转换。

我也对什么是保证感兴趣。我从未见过像你这样的例子会引起问题;然而,我知道获取与线程标识(在我们的例子中是互斥)相关的资源在过去曾导致我的团队出现问题。我会把它挖出来的。给你(标记为dupe):@spender我理解你的问题,它问的是是否在同一个服务实例上调用了start/stop方法。这不是我要问的。我假设这两个方法都在同一个实例上被调用,并且超出了这个范围,我会问一个关于开始/停止和整个开始/停止的分配可见性的特定问题。@LawrenceJohnston:这是一样的。如果是同一个例子,那么如果你之前分配给它的东西被邪恶势力神秘地重新分配,那么就会有严重的错误。简言之,我的问题的答案隐含地确认了您在
OnStart
中所做的作业仍将在
OnStop
中。如果不是这样的话,我会停止使用.Net,因为这就像在流沙上编程一样。“如果在
new
完成之前调用stop事件,则可能会发生
\u foo
为空的情况;”根据我回答中的示例,这不是真的。“而且,
\u foo
可以在代码中的[另一个]位置发布,因此[这是]一个先检查的好做法。”是的。@J0e3gan不错的地方我假设可以从不同的位置调用OnStart和OnStop,但是的,SCM不允许您随机执行此操作。