C# 对.NET中的服务使用Task.WaitAll()时出现问题
我有一个具有以下OnStart()方法的服务: 和OnStop()方法: StartManager()方法的实现如下所示:C# 对.NET中的服务使用Task.WaitAll()时出现问题,c#,.net,task-parallel-library,task,C#,.net,Task Parallel Library,Task,我有一个具有以下OnStart()方法的服务: 和OnStop()方法: StartManager()方法的实现如下所示: public void StartManager() { foreach (var reportGeneratorThread in this.ReportGenerators) { reportGeneratorThread.Start(); Thread.Sleep(1000); }
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()方法中,因为reportGeneratorRead.Start()方法正在启动一个任务,该任务在while循环中连续生成一个报告,只有在抛出一个取消令牌并且我在我的service 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);
}
}
}
如果不清楚地说明您的问题,就很难准确地理解代码在做什么。我马上就看到了至少一些奇怪的事情:
WaitAll()
将防止代码检测到哪怕一个故障。因此,它们要么都需要在重新启动之前出现故障,要么在实际停止服务并取消任务之前,您不会检测到故障v!=如果taskException
为null
,则taskException
将为true。所以在我看来,以后检查它是否为非null是毫无意义的OnStart()
方法在设计上需要及时返回,但您当前的实现没有做到这一点。通过将StartManager()
方法设置为async
方法,并使用wait
将控制权返回给调用方,直到发生有趣的事情,这个基本问题似乎是可以解决的。可能看起来像这样:
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"));
}
}
}
}
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;
}
}
}
}
然后您可以从OnStart()
调用它,如下所示:
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"));
}
}
}
}
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;
}
}
}
}
请注意,上面提出的更改并没有解决我提到的任何奇怪之处。在我看来,该方法更有用的实现可能如下所示:
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"));
}
}
}
}
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()
传播异常的细节。我认为我的示例是对的,但完全可能您需要调整细节以使其正常工作。我希望至少能以一种有用的方式表达基本思想。您不需要任务。WaitAll
。请详细解释您为什么认为需要WaitAll()
,我没有看到你做任何事情,除了处理可以在运行的方法reportGeneratorThread
中完成的错误。你在哪里检查取消?您是否假设调用.Cancel()
会在任务中引发异常?为了避免混淆,我以前没有在AggregateException中包含我的代码,但现在已经更新了它。我需要使用Task.WaitAll()方法来重新启动出现故障的线程。感谢您的回复。我能问你为什么在你的提案中不使用AggregateException吗?@OsmanEsen:仅在第二个例子中。正如我所指出的,虽然我无法确定如何从WhenAny()
报告异常,但我记得WhenAny()
只是返回一个未打包的异常,而不是像WhenAll()
那样返回一个aggregateeexception
。我今天还没有费心去测试这个,但似乎证实了我的假设。我已经实现了这个建议,但我甚至无法使用Task捕捉到一般异常。WhenAny()@OsmanEsen:对不起,我的错误。我忘记了WhenAny()
返回已完成的任务。然后,您需要等待任务
对象(该对象随后将引发异常),或者需要明确检查对象的状态,并从任务.exception
属性检索任何异常。我将更新代码示例以进行说明。非常感谢。此解决方案是否会导致死锁(异步等待)?。