C# Windows服务在正常关机的情况下对计时器执行工作

C# Windows服务在正常关机的情况下对计时器执行工作,c#,C#,我正在尝试创建一个Windows服务,它可以在计时器上执行作业,并且可以正常关闭。我在这里使用了各种各样的问题/答案来产生下面的代码。它可以工作,但我想确保它是最正确和优雅的解决方案。我也有一些具体的问题(在代码之后) 这是主要的服务类 using System; using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; using System.Timers; namespace My

我正在尝试创建一个Windows服务,它可以在计时器上执行作业,并且可以正常关闭。我在这里使用了各种各样的问题/答案来产生下面的代码。它可以工作,但我想确保它是最正确和优雅的解决方案。我也有一些具体的问题(在代码之后)

这是主要的服务类

using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;

namespace MyService
{

    public class MyService : ServiceBase
    {

        CancellationTokenSource cancellationTokenSource;
        System.Timers.Timer serviceTimer;
        Task workTask;

        public static void Main(string[] args)
        {
            if (!Environment.UserInteractive)
            {
                Run(new MyService());
            }
        }

        protected override void OnStart(string[] args)
        {
            cancellationTokenSource = new CancellationTokenSource();

            serviceTimer = new System.Timers.Timer(30000);
            serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
            serviceTimer.Start();
        }

        protected override void OnStop()
        {
            try
            {
                serviceTimer.Stop();
                cancellationTokenSource.Cancel();

                if (workTask != null)
                {
                    workTask.Wait(10000);
                }
            }
            finally
            {
                serviceTimer.Dispose();
                serviceTimer = null;

                cancellationTokenSource.Dispose();
                cancellationTokenSource = null;
            }
        }

        private void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            serviceTimer.Stop();

            workTask = Task.Run(() => StartWorkMethod()).ContinueWith(WorkCompleted);
        }

        private void WorkCompleted(Task completedTask)
        {
            workTask = null;

            serviceTimer.Start();
        }

        private void StartWorkMethod()
        {
            Work work = new Work(cancellationTokenSource.Token);
            work.StartWork();
        }

    }

}
这是执行(当前模拟)工作的类

使用系统线程;
命名空间MyService
{
公开课
{
取消令牌取消令牌;
公共工作(取消令牌取消令牌)
{
this.cancellationToken=cancellationToken;
}
公共网络
{
对于(int i=0;i<4;i++)
{
if(cancellationToken.IsCancellationRequested)
{
打破
}
睡眠(10000);
}
}
}
}
该服务在不阻塞处理程序线程的情况下工作并运行所有任务。如果服务停止,OnStop方法将等待任务的当前工作块完成一段时间,然后再停止(感谢Oz的Ian!)

以下是我的具体问题:

  • 为了防止服务立即停止并等待当前块完成,我使用
    working
    变量和while循环等待
    Work
    类完成,并将bool设置为
    false
    。这是最好的处理方法吗?已经由奥兹的伊恩回答了
  • 我还希望有一个“功能”,如果当前块需要很长时间才能完成,那么
    OnStop
    方法在退出之前只会等待一定的时间。实现这一点的最佳方式是什么?已经由奥兹的伊恩回答了
  • 我已经试着用我的代码处理所有线程问题。我是否遗漏了任何东西,或者可能在以后的实现中导致问题
  • 为避免混淆,还应注意以下事项:

    • 服务安装代码不包括在内,我正在使用安装程序安装服务
    • 计时器控制两次执行之间的时间,以便在前一次执行耗时较长时不会出现重复执行;这就是为什么计时器在开始工作之前停止,在开始工作之后重新启动
    • 我已经看到,
      Main
      方法有时放在它自己的文件中,但大多数情况下可执行文件也是安装程序;在这种情况下,它只会通过
      Main
      方法本身简化此文件

    编辑以合并Ian of Oz的建议。

    您是否可以保留在serviceTimer_中创建的任务,然后添加对任务的调用。在调用取消后等待(毫秒)TokenSource.Cancel()?@IanofOz,谢谢!我测试了一下,它成功了。我想,除了我的布尔值之外,我还需要添加一个计时器来跟踪是否有工作发生。但是Task.Wait替换了布尔值,并处理了等待x时间部分。好主意!很高兴任务的想法奏效了,太棒了。你可以考虑的另一种方法是,如果你需要的话,推迟服务的停止。默认情况下,您有20秒(或者至少过去是这样,在我的Windows 8机器上检查,现在的默认时间是5秒)。这是由HKLM\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout处的注册表项控制的。抱歉,此处被切断。。。如果您需要更多的时间(例如完成任务),可以在ServiceBase上调用RequestAditionalTime来延迟服务的完成。您还可以将WaitToKillServiceTimeout值用于任务的较少硬编码值。等待超时值。
    using System.Threading;
    
    namespace MyService
    {
    
        public class Work
        {
    
            CancellationToken cancellationToken;
    
            public Work(CancellationToken cancellationToken)
            {
                this.cancellationToken = cancellationToken;
            }
    
            public void StartWork()
            {
                for (int i = 0; i < 4; i++)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        break;
                    }
    
                    Thread.Sleep(10000);
                }
            }
    
        }
    
    }