.NET Windows服务需要使用STAThread。

15

我创建了一个 Windows 服务,将调用一些 COM 组件,因此我在 Main 函数上标记了 [STAThread]。然而,当定时器触发时,它报告 MTA 并且 COM 调用失败。我该如何解决这个问题?

using System;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
using System.Timers;



namespace MyMonitorService
{
    public class MyMonitor : ServiceBase
    {
        #region Members
        private System.Timers.Timer timer = new System.Timers.Timer();
        #endregion

        #region Construction
        public MyMonitor ()
        {
            this.timer.Interval = 10000; // set for 10 seconds
            this.timer.Elapsed += new System.Timers.ElapsedEventHandler(this.timer_Elapsed);
        }
        #endregion

        private void timer_Elapsed (object sender, ElapsedEventArgs e)
        {
            EventLog.WriteEntry("MyMonitor", String.Format("Thread Model: {0}", Thread.CurrentThread.GetApartmentState().ToString()), EventLogEntryType.Information);
        }

        #region Service Start/Stop
        [STAThread]
        public static void Main ()
        {
            ServiceBase.Run(new MyMonitor());
        }

        protected override void OnStart (string[] args)
        {
            EventLog.WriteEntry("MyMonitor", "My Monitor Service Started", EventLogEntryType.Information);
            this.timer.Enabled = true;
        }

        protected override void OnStop ()
        {
            EventLog.WriteEntry("MyMonitor", "My Monitor Service Stopped", EventLogEntryType.Information);
            this.timer.Enabled = false;
        }
        #endregion
    }
}
5个回答

28

服务由Windows服务托管系统运行,该系统使用MTA线程运行。您无法控制此过程。您必须创建一个新的线程并将其ApartmentState设置为STA,然后在此线程上执行工作。

这是一个扩展ServiceBase类的示例:

public partial class Service1 : ServiceBase
{
    private System.Timers.Timer timer;

    public Service1()
    {
        InitializeComponent();
        timer = new System.Timers.Timer();
        this.timer.Interval = 10000; // set for 10 seconds
        this.timer.Elapsed += new System.Timers.ElapsedEventHandler(Tick);
    }

    protected override void OnStart(string[] args)
    {
        timer.Start();
    }

    private void Tick(object sender, ElapsedEventArgs e)
    {
        // create a thread, give it the worker, let it go
        // is collected when done (not IDisposable)
        var thread = new Thread(WorkerMethod);
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        OnStop(); // kill the timer
    }

    private void WorkerMethod(object state)
    {
        // do your work here in an STA thread
    }

    protected override void OnStop()
    {
        timer.Stop();
        timer.Dispose();
    }
}

请注意,此代码实际上并不会停止服务,而是停止计时器。可能仍然有许多工作正在多个线程上进行。例如,如果您的工作包括从大型数据库运行多个查询,那么您可能会因为同时运行太多线程而崩溃。
在这种情况下,我会创建一定数量的STA线程(也许是核心数的2倍),这些线程监视线程安全队列以获取工作项。计时器滴答事件将负责使用需要完成的工作项加载该队列。
所有这一切都取决于您每十秒钟实际上要做什么,是否应该在计时器滴答下次完成,以及在这种情况下应该采取什么措施等。

1
+1 对于解决方法;但是,你如何解释控制台应用程序存在同样的问题呢?根据我的理解,虽然您的解释似乎不正确。服务作为服务控制管理器的子进程而不是线程启动,然后调用“StartServiceCtrlDispatcher”连接到控制器(从我在反射中看到的),因此以STA启动服务应该是可能的。 - Dirk Vollmar
1
它不存在于控制台应用程序中。控制台应用程序遵循STAThreadAttribute。服务不遵循。SCM不在STA线程中运行。SCM既不尊重也不提供任何方法让服务通信其所需的公寓状态。OP的代码令人困惑,因为它包括Main方法。所有Main方法做的就是说,“嘿,SCM,在这里有一个包含我要运行的服务类型的DLL,请运行它,kthxbai”,然后消失了。是的,它在STA线程中运行,但它不会阻塞并立即返回。然后SCM使用自己的线程确定何时运行服务。 - user1228
1
啊啊,Divo...我理解你的困惑。框架中有两种不同类型的计时器——一种使用线程池,另一种向UI线程发送Windows消息。线程池计时器(例如System.Threading.Timer)在线程池上排队工作,该线程池仅包含MTA线程。而System.Windows.Forms.Timer等UI线程计时器则向应用程序的主窗口发送一个消息,该消息表示“嘿,运行这个方法!”,因此在STA线程中运行(不建议在窗体应用程序之外使用!)。 - user1228
1
如果线程A是STA,并在线程池中排队一个方法,该方法将始终在MTA线程上运行。永远。不会改变。不同的线程运行方法,明白吗?如果这让您感到困惑,请获取《CLR Via C#》一书。此外,“(.NET)是可执行文件而不是dll”对我来说毫无意义。您提供的链接与正在讨论的主题不符,请参阅http://msdn.microsoft.com/en-us/library/tks2stkt.aspx。 - user1228
@Will:如果你用 Reflector 看的话,你会发现 ServiceBase.Run 调用了我之前提到的方法。服务很好地运行在自己的进程中作为一个独立的可执行文件,而不是加载到服务控制器中的 dll。它只是通过使用 StartServiceCtrlDispatcher 函数建立的连接由 SCM 控制。你也可以将 dll 作为服务运行,但这个 dll 将在一个通用进程(svchost.exe)中托管。 - Dirk Vollmar
显示剩余7条评论

5

在服务中这是行不通的,调用你的Main()方法的线程已经由服务管理器启动。您需要创建一个单独的线程,并使用Thread.SetApartmentState()进行初始化,并泵送消息循环。


我认为问题在于计时器线程的线程公寓,因为我在控制台应用程序中遇到了相同的问题(主线程是STA,但在计时器事件中线程公寓是MTA)。 - Dirk Vollmar
是的 - 计时器将成为 MTA 线程。我在我的答案中特别提到了这一点。 - Reed Copsey

5

在服务中设置STAThread属性不起作用。由于处理方式与应用程序不同,因此这将被忽略。

我的建议是为您的服务手动创建单独的线程,设置其公寓状态,并将所有内容移至其中。这样,您可以正确设置线程为STA。

但是,这里会有另一个问题-您需要重新设计服务的工作方式。您不能仅使用System.Threading.Timer实例来计时,因为它运行在单独的线程上,该线程将不是STA。当其经过事件被触发时,您将在不同的非STA线程上工作。

而您可能希望在显式创建的线程中执行主要工作,而非在计时器事件中执行。您可以在该线程中具有重置事件以阻塞,并使计时器“设置”该事件以允许您的逻辑在STA线程中运行。


1
网上有很多关于在服务的主函数中使用STAThread的例子,它们是错误的吗? - Tim Hoolihan
大多数都不起作用。服务由服务管理器在单独的线程中启动,并且不像应用程序一样使用Main例程。 - Reed Copsey
OP使用了一个System.Timers.Timer(仍在MTA上运行)。然而,我仍然不明白为什么STAThread不能在服务中工作? - Dirk Vollmar
Main方法立即返回。它所做的只是告诉SCM应该运行特定dll中定义的特定服务类型。然后,SCM使用自己的线程确定何时(以及是否)运行服务。 - user1228

0

看一个类似的例子:http://www.aspfree.com/c/a/C-Sharp/Creating-a-Windows-Service-with-C-Sharp-introduction/1/

如果你的主要方法是...

    [STAThread]
    public static void Main ()
    {
        MyMonitor m = new MyMonitor();
        m.Start();
    }

将计时器的开始/停止移出事件...
 public void Start() { this.timer.Enabled = true;}
 public void Stop() { this.timer.Enabled = false;}

  protected override void OnStart (string[] args)
    {
        EventLog.WriteEntry("MyMonitor", "My Monitor Service Started", EventLogEntryType.Information);
    }

    protected override void OnStop ()
    {
        EventLog.WriteEntry("MyMonitor", "My Monitor Service Stopped", EventLogEntryType.Information);
    }

0

这里报告它正在使用STA。它基于Will的建议和http://en.csharp-online.net/Creating_a_.NET_Windows_Service%E2%80%94Alternative_1:_Use_a_Separate_Thread

using System;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;



namespace MyMonitorService
{
    internal class MyMonitorThreaded : ServiceBase
    {
        private Boolean bServiceStarted = false;
        private Thread threadWorker;

        private void WorkLoop ()
        {
            while (this.bServiceStarted)
            {
                EventLog.WriteEntry("MyMonitor", String.Format("Thread Model: {0}", Thread.CurrentThread.GetApartmentState().ToString()), EventLogEntryType.Information);

                if (this.bServiceStarted)
                    Thread.Sleep(new TimeSpan(0, 0, 10));
            }

            Thread.CurrentThread.Abort();
        }

        #region Service Start/Stop
        protected override void OnStart (String[] args)
        {
            this.threadWorker = new Thread(WorkLoop);
            this.threadWorker.SetApartmentState(ApartmentState.STA);
            this.bServiceStarted = true;
            this.threadWorker.Start();
        }

        protected override void OnStop ()
        {
            this.bServiceStarted = false;
            this.threadWorker.Join(new TimeSpan(0, 2, 0));
        }
        #endregion
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接