运行异步代码的Windows服务不等待工作完成

时间:2015-03-20 14:32:28

标签: c# parallel-processing windows-services async-await task-parallel-library

简介

我有一个Windows服务,可以并行执行多个作业作为异步任务。然而,当调用OnStop时,似乎这些都被立即终止,而不是被允许以更加亲切的方式停止。

更详细

每个工作代表一次工作的迭代,因此完成工作后,工作需要再次运行。

为了实现这一目标,我正在编写一个概念验证Windows服务:

  • 将每个作业作为等待的异步TPL任务运行(这些都是I / O绑定任务)
  • 每个作业在循环中迭代运行
  • 每个作业的循环并行运行

当我运行服务时,我看到所有内容都按预期执行。但是,当我停止服务时,似乎一切都停止了。

好的 - 这是怎么回事?

在服务中,我有一个取消令牌和一个TaskCompletion Source:

private static CancellationTokenSource _cancelSource = new CancellationTokenSource();
private TaskCompletionSource<bool> _jobCompletion = new TaskCompletionSource<bool>();
private Task<bool> AllJobsCompleted { get { return _finalItems.Task; } }

这个想法是,当每个Job都优雅地停止时,Task AllJobsCompleted将被标记为已完成。

OnStart只是开始运行这些作业:

protected override async void OnStart(string[] args)
{
    _cancelSource = new CancellationTokenSource();  
    var jobsToRun = GetJobsToRun(); // details of jobs not relevant 
    Task.Run(() => this.RunJobs(jobsToRun, _cancelSource.Token).ConfigureAwait(false), _cancelSource.Token);
}

Task RunJobs将以并行循环运行每个作业:

private async Task RunModules(IEnumerable<Jobs> jobs, CancellationToken cancellationToken)
{
    var parallelOptions = new ParallelOptions { CancellationToken = cancellationToken };    
    int jobsRunningCount = jobs.Count();
    object lockObject = new object();

    Parallel.ForEach(jobs, parallelOptions, async (job, loopState) =>
    {
        try
        {
            do
            {
                await job.DoWork().ConfigureAwait(false); // could take 5 seconds
                parallelOptions.CancellationToken.ThrowIfCancellationRequested();
            }while(true);
        }
        catch(OperationCanceledException)
        {
            lock (lockObject) { jobsRunningCount --; }
        }
    }); 

    do
    {
        await Task.Delay(TimeSpan.FromSeconds(5));
    } while (modulesRunningCount > 0);

    _jobCompletion.SetResult(true);
}

所以,应该发生的事情是,当每个作业完成当前的迭代时,应该看到取消已经发出信号,然后它应该退出循环并递减计数器。

然后,当jobsRunningCount达到零时,我们更新TaskCompletionSource。 (可能有一种更优雅的方式来实现这一目标......)

所以,对于OnStop:

protected override async void OnStop()
{
    this.RequestAdditionalTime(100000); // some large number        
    _cancelSource.Cancel();     
    TraceMessage("Task cancellation requested."); // Last thing traced

    try
    {
        bool allStopped = await this.AllJobsCompleted;          
        TraceMessage(string.Format("allStopped = '{0}'.", allStopped));
    }
    catch (Exception e)
    {
        TraceMessage(e.Message);
    }
} 

我的期望是:

  1. 单击服务
  2. 上的[停止]
  3. 服务应该花一些时间停止
  4. 我应该看到跟踪声明“请求取消任务。”
  5. 我应该看到一个跟踪语句,说“allStopped = true”或异常消息
  6. 当我使用WPF Form应用程序调试时,我得到了这个。

    但是,当我将其作为服务安装时:

    1. 单击服务
    2. 上的[停止]
    3. 服务几乎立即停止
    4. 我只看到跟踪声明“请求取消任务。”
    5. 我需要做些什么来确保OnStop不会终止我的并行异步作业并等待TaskCompletionSource?

1 个答案:

答案 0 :(得分:11)

您的问题是OnStopasync void。因此,当它await this.AllJobsCompleted时,实际发生的是OnStop返回,SCM将其解释为已停止,并终止该过程。

这是您需要阻止任务的罕见情况之一,因为在任务完成之前您不能允许OnStop返回。

这应该这样做:

protected override void OnStop()
{
  this.RequestAdditionalTime(100000); // some large number        
  _cancelSource.Cancel();     
  TraceMessage("Task cancellation requested."); // Last thing traced

  try
  {
    bool allStopped = this.AllJobsCompleted.GetAwaiter().GetResult();          
    TraceMessage(string.Format("allStopped = '{0}'.", allStopped));
  }
  catch (Exception e)
  {
    TraceMessage(e.Message);
  }
}