创建 C# 窗口服务以轮询数据库

本文关键字:数据库 服务 窗口 创建 | 更新日期: 2023-09-27 18:37:27

我想编写一个服务来轮询数据库并根据返回的数据执行操作。

不确定这样做的最佳方法是什么,我可以找到一些关于它的博客和这个堆栈溢出问题轮询服务 - C#。但是,我很担心它们都很旧,可能已经过时了。

任何人都可以就做这样的事情的当前建议或最佳实践(如果有的话)向我提供建议,或者为我指出最近一篇关于此的博客文章的方向。从我可以使用计时器或 tpl 任务收集的内容来看,有两种潜在的方法可以做到这一点。

如果仍然建议计时器,那么当服务停止时它们将如何工作,因为我打算让这些服务执行的操作可能需要 30+ 分钟,这就是为什么我说使用任务,因为我可以使用任务取消令牌,但这些在取消时会抛出异常(如果我错了,请纠正我),我认为我真的不想要这种行为(尽管如果您认为有一个原因我会想要那个)。

抱歉,我可能会在一个问题中问很多问题,但我不完全确定自己在问什么。

创建 C# 窗口服务以轮询数据库

为此

使用Windows服务。使用计划任务本身并不是一个坏主意,但是既然您说民意调查可以每 2 分钟发生一次,那么您可能最好使用该服务。该服务将允许您在民意调查之间保持状态,并且您还可以更好地控制民意调查的时间。您说一旦启动,操作可能需要30 +分钟,所以也许您希望将民意调查推迟到操作完成。当逻辑作为服务运行时,这更容易做到。

最后,您使用什么机制来生成民意调查并不重要。您可以使用计时器或睡眠的专用线程/任务或其他任何东西。就个人而言,我发现专用线程/任务比计时器更容易用于此类事情,因为它更容易控制轮询间隔。此外,您绝对应该使用 TPL 提供的协作取消机制。它不需要引发异常。仅当您调用 ThrowIfCancellationRequested 时,它才会这样做。您可以改用IsCancellationRequested来检查取消令牌的状态。

这是一个非常通用的模板,您可以使用它来开始使用。

public class YourService : ServiceBase
{
  private CancellationTokenSource cts = new CancellationTokenSource();
  private Task mainTask = null;
  protected override void OnStart(string[] args)
  {
    mainTask = new Task(Poll, cts.Token, TaskCreationOptions.LongRunning);
    mainTask.Start();
  }
  protected override void OnStop()
  {
    cts.Cancel();
    mainTask.Wait();
  }
  private void Poll()
  {
    CancellationToken cancellation = cts.Token;
    TimeSpan interval = TimeSpan.Zero;
    while (!cancellation.WaitHandle.WaitOne(interval))
    {
      try 
      {
        // Put your code to poll here.
        // Occasionally check the cancellation state.
        if (cancellation.IsCancellationRequested)
        {
          break;
        }
        interval = WaitAfterSuccessInterval;
      }
      catch (Exception caught)
      {
        // Log the exception.
        interval = WaitAfterErrorInterval;
      }
    }
  }
}

就像我说的,我通常使用专用线程/任务而不是计时器。我这样做是因为我的轮询间隔几乎从不恒定。如果检测到暂时性错误(如网络或服务器可用性问题),我通常会开始减慢轮询速度,这样我的日志文件就不会快速连续地一遍又一遍地填满相同的错误消息。

你有几个选择。若要从本质上最简单的选项开始,你可以决定将应用创建为控制台应用程序,并将可执行文件作为 Windows 任务计划程序中的任务运行。您需要做的就是将可执行文件指定为要在任务中启动的程序,并让任务计划程序为您处理计时间隔。如果您不关心状态,这可能是首选方法,并且如果您不需要,则可以防止您担心创建和管理 Windows 服务。有关如何使用调度程序,请参阅以下链接。

视窗任务计划程序

您可以

执行此操作的下一个方法是创建一个Windows服务,并在该服务中使用计时器,特别是System.Timers.Timer。实质上,您将计时器间隔设置为在运行进程之前希望经过的时间量。然后,您将注册计时器tick事件,该事件将在每次间隔发生时触发。在这种情况下,您基本上拥有要运行的进程;如果您愿意,这可以启动附加线程。然后,在初始设置之后,您只需调用计时器 Start() 函数或将 Enabled 属性设置为 True 即可启动计时器。可以在描述对象的 MSDN 页面上的示例中找到一个很好的示例。有很多教程展示了如何设置Windows服务,所以我不会专门讨论它。

MSDN: System.Timers.Timer

最后,更复杂的是设置一个侦听SqlDependency的Windows服务。如果应用程序外部的数据库中可能发生某些事情,但您需要在应用程序或其他服务中了解它,则此技术非常有用。以下链接提供了有关如何在应用程序中设置 SqlDependency 的良好教程。

使用 SqlDependency 监视 SQL 数据库更改


我想从您的原始帖子中指出两件事,它们并不特定于您的问题。

  1. 如果您正在编写真正的 Windows 服务,则不希望该服务停止。服务应持续运行,如果确实发生异常,则应正确处理,而不是停止服务。

  2. 取消令牌不必抛出异常;只需不调用 ThrowIfCancelRequest() 将导致不引发异常,或者如果这是一个 CancelTokenSource,则在 Cancel 方法上将参数设置为 false,然后随后检查令牌以查看是否在线程中请求取消,如果是,则优雅地返回线程。

例如:

    CancellationTokenSource cts = new CancellationTokenSource();
    ParallelOptions options = new ParallelOptions
    {
        CancellationToken = cts.Token
    };
    Parallel.ForEach(data, options, i =>
    {
        try
        {
            if (cts.IsCancellationRequested) return;
            //do stuff
        }
        catch (Exception ex)
        {
            cts.Cancel(false);
        }
    });