Windows Service running Async code not waiting on work to complete

五迷三道 提交于 2020-01-31 08:40:27

问题


In Brief

I have a Windows Service that executes several jobs as async Tasks in parallel. However, when the OnStop is called, it seems that these are all immediately terminated instead of being allowed to stop in a more gracious manner.

In more detail

Each job represents an iteration of work, so having completed its work the job then needs to run again.

To accomplish this, I am writing a proof-of-concept Windows Service that:

  • runs each job as an awaited async TPL Task (these are all I/O bound tasks)
  • each job is run iteratively within a loop
  • each job's loop is run in parallel

When I run the Service, I see everything executing as I expect. However, when I Stop the service, it seems that everything stops dead.

Okay - so how is this working?

In the Service I have a cancellation token, and a TaskCompletion Source:

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

The idea is that when every Job has gracefully stopped, then the Task AllJobsCompleted will be marked as completed.

The OnStart simply starts running these jobs:

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);
}

The Task RunJobs will run each job in a parallel loop:

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);
}

So, what should be happening is that when each job finishes its current iteration, it should see that the cancellation has been signalled and it should then exit the loop and decrement the counter.

Then, when jobsRunningCount reaches zero, then we update the TaskCompletionSource. (There may be a more elegant way of achieving this...)

So, for the 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);
    }
} 

What what I expect is this:

  1. Click [STOP] on the Service
  2. The Service should take sometime to stop
  3. I should see a trace statement "Task cancellation requested."
  4. I should see a trace statement saying either "allStopped = true", or the exception message

And when I debug this using a WPF Form app, I get this.

However, when I install it as a service:

  1. Click [STOP] on the Service
  2. The Service stops almost immediately
  3. I only see the trace statement "Task cancellation requested."

What do I need to do to ensure the OnStop doesn't kill off my parallel async jobs and waits for the TaskCompletionSource?


回答1:


Your problem is that OnStop is async void. So, when it does await this.AllJobsCompleted, what actually happens is that it returns from OnStop, which the SCM interprets as having stopped, and terminates the process.

This is one of the rare scenarios where you'd need to block on a task, because you cannot allow OnStop to return until after the task completes.

This should do it:

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);
  }
}


来源:https://stackoverflow.com/questions/29168992/windows-service-running-async-code-not-waiting-on-work-to-complete

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!