5

当通过服务控制管理器运行时,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;
    }

}

我无法保证我的示例中的异常不会被抛出,但我发现的所有示例,包括StackOverflow 上的其他地方,似乎都假设,例如,对 OnStart() 中的变量的赋值将始终在 OnStop() 中可见。

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

4

2 回答 2

2

从某种意义上说,SCM 不能保证你概述的异常不会被抛出。当然,它不控制服务对私有成员的操作——例如,如果附加服务代码影响_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)打开一个命令外壳。

3) 打开另一个命令外壳。

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

MyService 服务正在启动.....

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

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

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

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

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

MyService 服务已成功启动。

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

MyService 服务正在停止。

MyService 服务已成功停止。

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

2013 年 10 月 22 日上午 7:28:55:在线程 ID 4 上的 OnStart(string[]) 中。睡眠 10 秒...

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

我认为使用两个命令 shell 可以更轻松地快速启动和停止服务;但是该示例也与一个命令外壳类似地工作。

最后,您可能会发现 Mitchell Taylor (CoolDadTx)在 MSDN 论坛中对类似问题的回答和我一样有趣:

SCM 使用的线程模型没有正式记录 AFAIK。众所周知,每个服务都在自己的线程上被调用。但是,SCM 可能会也可能不会使用线程池来跨服务重用线程。当服务被调用(启动、停止、自定义命令等)时,它应该执行其任务并快速返回。可以花费多长时间有很大的限制。除了快速返回之外,您还需要将请求推送到辅助线程进行处理。SCM 本身在单独的线程上运行,因此如果服务响应时间过长,则 SCM 将其视为挂起。这在这里讨论:http: //msdn.microsoft.com/en-us/library/ms684264 (VS.85).aspx

更新:

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

于 2013-10-22T04:09:15.040 回答
0

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

_foo = new VeryLongAndTimeConsummingTask();

[编辑]:正如评论中提到的,SCM 阻止 OnStop 在 OnStart 完成之前运行。此评论来自我可能将 start 和 stop 包装器公开的不良做法。

如果在新完成之前调用停止事件,则_foo 可能为空;

并且 _foo 可以在代码中的加法器位置释放,因此首先检查它是一个好习惯。

于 2013-10-21T09:41:41.150 回答