【问题标题】:Do Windows services need to ensure that commands can be processed on different threads?Windows 服务是否需要确保命令可以在不同的线程上处理?
【发布时间】:2013-08-25 19:50:07
【问题描述】:

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

}

我无法保证我的示例中的异常不会被抛出,但我发现的所有示例,包括elsewhere on StackOverflow,似乎都假设,例如,在 OnStart( ) 在 OnStop() 中始终可见。

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

【问题讨论】:

  • 我对保证的内容也很感兴趣。我从来没有见过像你的例子这样的东西会导致问题;但是,我确实知道过去获取与线程身份相关的资源(在我们的例子中是互斥体)给我的团队带来了问题。
  • 我之前也问过同样的问题。我会挖出来的。
  • 你在这里(标记为骗子):Calling ServiceBase.OnStart and OnStop... same instance?
  • @spender 据我了解,您的问题是询问是否在同一服务实例上调用了启动/停止方法。这不是我要问的。我假设这两种方法都是在同一个实例上调用的,并且超出了这个范围以询问有关启动/停止和跨启动/停止分配可见性的特定问题。
  • @LawrenceJohnston:是一回事。如果它是同一个实例,那么如果你之前分配给它的东西被邪恶的力量神秘地重新分配,那么就会出现严重错误。简而言之,我的问题的答案含蓄地确认您在OnStart 中所做的分配仍将存在于OnStop。如果不是这种情况,我会停止使用 .Net,因为它就像在流沙上编程。

标签: c# windows-services service-control-manager


【解决方案1】:

从某种意义上说,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 中,尝试在 10 秒前停止服务(使用net stop)。你应该看到:

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

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

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 可以更轻松地快速启动和停止服务;但是这个例子也同样适用于一个命令 shell。

最后,您可能会发现 Mitchell Taylor (CoolDadTx) 对a similar question in MSDN forums 的回答和我一样有趣:

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

更新:

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

【讨论】:

    【解决方案2】:

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

    _foo = new VeryLongAndTimeConsummingTask();
    

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

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

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

    【讨论】:

    • "如果在new 完成之前调用停止事件,则_foo 可能为空;"根据我的答案中的示例,这不正确。 “而且_foo 可以在代码中的[另一个] 位置发布,所以[这是] 一个很好的做法,首先检查。”是的。
    • @J0e3gan 不错的地方,我认为 OnStart 和 OnStop 可以从不同的地方调用,但是是的,SCM 不允许你随机调用。
    猜你喜欢
    • 1970-01-01
    • 2012-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-15
    • 1970-01-01
    • 2014-12-16
    相关资源
    最近更新 更多