20

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

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

任何人都可以就当前的建议或最佳实践(如果有的话)建议我做这样的事情,或者指出我最近关于此的博客文章的方向。据我所知,使用计时器或 tpl 任务是两种可能的方法。

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

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

4

2 回答 2

31

为此,请使用 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;
      }
    }
  }
}

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

于 2013-10-30T19:48:24.957 回答
4

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

Windows 任务计划程序

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

MSDN:系统.计时器.计时器

最后,更复杂的是设置一个侦听 SqlDependency 的 Windows 服务。如果事情可能发生在您的应用程序之外的数据库中,但您需要在您的应用程序或其他一些服务中意识到它,则此技术很有用。以下链接有一个关于如何在应用程序中设置 SqlDependency 的很好的教程。

使用 SqlDependency 监控 SQL 数据库更改


我想从您的原始帖子中指出两件事,这些事情并非针对您的问题。

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

  2. 取消令牌不必抛出异常;根本不调用 ThrowIfCancellationRequested() 将导致不抛出异常,或者如果这是 CancellationTokenSource 在 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);
        }
    });  


于 2013-10-30T19:43:36.830 回答