15

我创建了一个将调用一些 COM 组件的 Windows 服务,因此我将 [STAThread] 标记到 Main 函数。但是,当计时器触发时,它会报告 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
    }
}
4

5 回答 5

28

服务由 Windows 服务托管系统运行,该系统使用 MTA 线程运行。你无法控制这一点。您必须创建一个新Thread并将其 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 倍)来监控工作项的线程安全队列。计时器滴答事件将负责加载需要完成的工作的队列。

这一切都取决于你实际上每十秒在做什么,是否应该在下次计时器滴答时完成,在这种情况下你应该做什么等等。

于 2010-01-04T19:56:05.523 回答
5

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

于 2010-01-04T19:48:34.883 回答
5

设置 STAThread 属性不适用于服务。它的处理方式与应用程序不同,因此将被忽略。

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

然而,这里还有另一个问题——你必须重新设计你的服务的工作方式。您不能只使用System.Threading.Timer实例进行计时 - 它在单独的线程上运行,该线程不会是 STA。当它的 elapsed 事件触发时,您将在另一个非 STA 线程上工作。

您可能希望在明确创建的线程中完成主要工作,而不是在计时器事件中完成工作。您可以在该线程中设置一个阻塞事件,并让您的计时器“设置”它以允许您的逻辑在 STA 线程中运行。

于 2010-01-04T19:49:29.887 回答
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);
    }
于 2010-01-04T19:49:11.293 回答
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
    }
}
于 2010-01-04T21:01:01.427 回答