使用Task.WaitAll()为.NET中的服务发出问题

时间:2015-10-26 19:41:16

标签: c# .net task-parallel-library task

我有一个服务,遵循OnStart()方法:

protected override void OnStart(string[] args)
{
  this.manager.StartManager();
  this.log.LogEvent(this.id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Service started"));
}

和OnStop()方法:

protected override void OnStop()
{      
  this.manager.CancellationTokenSource.Cancel();      
  this.log.LogEvent(this.id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Service stopped"));
}

StartManager()方法实现如下:

public void StartManager()
{
    foreach (var reportGeneratorThread in this.ReportGenerators)
    {                
        reportGeneratorThread.Start();
        Thread.Sleep(1000);
    }

    try
    { 
        Task.WaitAll(this.Tasks.ToArray());
    }
    catch (AggregateException e)
    {
        foreach (var v in e.InnerExceptions)
            {
                var taskException = v as TaskCanceledException;
                if (v != taskException)
                {
                    foreach (var reportGenerator in this.ReportGenerators)
                    {
                        if (reportGenerator.Task.IsFaulted)
                        {
                            this.logger.LogEvent(this.Id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Unhandled exception from Task " + reportGenerator.Task.Id));                             
                            ReportGeneratorThread faultedReportGeneratorThread = this.GetThreadById(reportGenerator.Task.Id);
                            var index = this.ReportGenerators.FindIndex(t => t.Equals(faultedReportGeneratorThread));
                            this.DisposeFaultedThread(faultedReportGeneratorThread, index);                           
                            this.ReportGenerators[index].Start();    
                            this.logger.LogDebug(string.Format(CultureInfo.InvariantCulture, "Faulted Task, and instance of ReportGeneratorThread is recreated and corresponding task is started"));
                            break;
                        }
                    }
                }
                else if (taskException != null)
                {
                    this.logger.LogEvent(this.Id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Task " + taskException.Task.Id + " has thrown a Task Canceled Exception"));
                }
            }
    }
}

问题出现在我的StartManager()方法中,因为reportGeneratorThread.Start()方法正在启动一个任务,该任务在while循环中连续生成报告,只有在抛出取消令牌并且我才会被中止。 ;我把它扔在我的服务OnStop()方法中。 因此,当我测试我的服务时,程序无法比Task.WaitAll()更进一步,这阻止我完成OnStart()方法,并且我收到以下错误:

 Error 1053: the service did not respond to the start or control request in a timely fashion    

我仍然需要管理我的任务,所以我实际上需要Task.WaitAll()方法,但我也需要解决这个问题。在这种情况下,如何完成OnStart()方法?在不改变代码结构的情况下,最好的方法是什么?

添加我的代码的更多部分:

这是我的任务调用的方法:

 private void DoWork()
    {
        while (this.Running)
        {
            this.GenerateReport();
            Thread.Sleep(Settings.Default.DefaultSleepDelay);
        }

        this.log.LogDebug(string.Format(CultureInfo.InvariantCulture, "Worker thread stopping."));
    }

和GenerateReport()方法:如果服务请求取消,我调用Stop()方法。此方法抛出TaskCancelledException。

 public void GenerateReport()
    {
        if (this.cancellationToken.IsCancellationRequested)
        {
            this.Stop();
        }

        var didwork = false;
        try
        {
            didwork = this.reportGenerator.GenerateReport(this.getPermission, this.TaskId);
        }
        catch (Exception e)
        {
            this.log.LogError(ReportGenerator.CorrelationIdForPickingReport, string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Error during report generation."), 0, e);
        }
        finally
        {
            if (!didwork)
            {
               Thread.Sleep(Settings.Default.ReportGenerationInterval);
            }
        }
    }

1 个答案:

答案 0 :(得分:2)

如果没有a good, minimal, complete code example来清楚地说明您的问题,则很难准确理解代码的作用。我发现至少有一些奇怪的事情发生了:

  1. 您的方法旨在重新启动报表生成器线程。但就我所知,它只能重启一个。如果您以后遇到任何其他故障,则没有代码等待检测并处理它。
  2. 使用WaitAll()将阻止代码检测到一个出现故障的代码,直到所有任务完成为止。所以他们要么在重新启动之前都需要故障,要么在实际停止服务并取消任务之前你不会检测到故障。
  3. 唯一的v != taskException时间是taskException null。所以在我看来,检查它以后是非空的是没有意义的。
  4. 在开始每项任务之间,我没有看到睡眠时间为1秒。
  5. 所以我不确定是否可以知道解决所有这些代码的最佳方法是什么。也就是说,似乎问题很明显:按照设计,您的OnStart()方法需要及时返回,但您当前的实现无法做到这一点。通过使StartManager()方法成为async方法并使用await将控制权返回给调用者,直到发生一些有趣的事情,这个基本问题似乎是可以解决的。这可能看起来像这样:

    public async Task StartManager()
    {
        foreach (var reportGeneratorThread in this.ReportGenerators)
        {                
            reportGeneratorThread.Start();
            Thread.Sleep(1000);
        }
    
        try
        { 
            await Task.WhenAll(this.Tasks.ToArray());
        }
        catch (AggregateException e)
        {
            foreach (var v in e.InnerExceptions)
                {
                    var taskException = v as TaskCanceledException;
                    if (v != taskException)
                    {
                        foreach (var reportGenerator in this.ReportGenerators)
                        {
                            if (reportGenerator.Task.IsFaulted)
                            {
                                this.logger.LogEvent(this.Id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Unhandled exception from Task " + reportGenerator.Task.Id));                             
                                ReportGeneratorThread faultedReportGeneratorThread = this.GetThreadById(reportGenerator.Task.Id);
                                var index = this.ReportGenerators.FindIndex(t => t.Equals(faultedReportGeneratorThread));
                                this.DisposeFaultedThread(faultedReportGeneratorThread, index);                           
                                this.ReportGenerators[index].Start();    
                                this.logger.LogDebug(string.Format(CultureInfo.InvariantCulture, "Faulted Task, and instance of ReportGeneratorThread is recreated and corresponding task is started"));
                                break;
                            }
                        }
                    }
                    else if (taskException != null)
                    {
                        this.logger.LogEvent(this.Id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Task " + taskException.Task.Id + " has thrown a Task Canceled Exception"));
                    }
                }
        }
    }
    

    然后你可以从OnStart()这样调用它:

    protected override void OnStart(string[] args)
    {
      // Save the returned Task in a local, just as a hack
      // to suppress the compiler warning about not awaiting the call.
      // Alternatively, store the Task object somewhere and actually
      // do something useful with it.
      var _ = this.manager.StartManager();
      this.log.LogEvent(this.id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Service started"));
    }
    

    请注意,上面提出的修改无法解决我提到的任何奇怪问题。在我看来,该方法的更有用的实现可能看起来像这样:

    public async Task StartManager()
    {
        foreach (var reportGeneratorThread in this.ReportGenerators)
        {                
            reportGeneratorThread.Start();
        }
    
        while (true)
        {
            try
            { 
                Task task = await Task.WhenAny(this.Tasks.ToArray());
    
                if (task.IsFaulted)
                {
                    // Unpack the exception. Alternatively, you could just retrieve the
                    // AggregateException directly from task.Exception and process it
                    // exactly as in the original code (i.e. enumerate the
                    // AggregateException.InnerExceptions collection). Note that in
                    // that case, you will see only a single exception in the
                    // InnerExceptions collection. To detect exceptions in additional
                    // tasks, you would need to await them as well. Fortunately,
                    // this will happen each time you loop back and call Task.WhenAny()
                    // again, since all the tasks are in the Tasks collection being
                    // passed to WhenAny().
    
                    await task;
                }
            }
            catch (Exception v)
            {
                var taskException = v as TaskCanceledException;
                if (v != taskException)
                {
                    foreach (var reportGenerator in this.ReportGenerators)
                    {
                        if (reportGenerator.Task.IsFaulted)
                        {
                            this.logger.LogEvent(this.Id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Unhandled exception from Task " + reportGenerator.Task.Id));                             
                            ReportGeneratorThread faultedReportGeneratorThread = this.GetThreadById(reportGenerator.Task.Id);
                            var index = this.ReportGenerators.FindIndex(t => t.Equals(faultedReportGeneratorThread));
                            this.DisposeFaultedThread(faultedReportGeneratorThread, index);                           
                            this.ReportGenerators[index].Start();    
                            this.logger.LogDebug(string.Format(CultureInfo.InvariantCulture, "Faulted Task, and instance of ReportGeneratorThread is recreated and corresponding task is started"));
                            break;
                        }
                    }
                }
                else
                {
                    this.logger.LogEvent(this.Id.ToString(), string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Task " + taskException.Task.Id + " has thrown a Task Canceled Exception"));
    
                    // Cancelling tasks...time to exit
                    return;
                }
            }
        }    
    }
    

    以上将循环,在发生故障时立即重启故障任务,但如果取消则完全退出。

    注意:缺少一个好的代码示例,上面是浏览器代码:完全未编译,未经测试。我不记得从WhenAll()WhenAny()传播异常的具体细节......我想我的例子是正确的,但你完全有可能需要调整它的具体细节。我希望至少基本思想能以有用的方式表达出来。