.net 在windows服务停止时优雅地关闭前台线程
在我的windows服务中,我创建了一个“父”前台线程,然后使用线程池(这意味着它们是后台线程)生成“子”线程来执行任务 在windows service stop上优雅地关闭前台线程的最佳方法是什么 以下是我当前的实现(从特定于任务的逻辑中剥离):.net 在windows服务停止时优雅地关闭前台线程,.net,multithreading,windows-services,threadpool,foreground,.net,Multithreading,Windows Services,Threadpool,Foreground,在我的windows服务中,我创建了一个“父”前台线程,然后使用线程池(这意味着它们是后台线程)生成“子”线程来执行任务 在windows service stop上优雅地关闭前台线程的最佳方法是什么 以下是我当前的实现(从特定于任务的逻辑中剥离): 公共部分类TaskScheduler:ServiceBase { 私有静态自动重置事件_finishedTaskAutoResetEvent=新自动重置事件(false); //此标志用于增加在服务停止时生成线程正常完成的机会。 私有布尔停止请求{
公共部分类TaskScheduler:ServiceBase
{
私有静态自动重置事件_finishedTaskAutoResetEvent=新自动重置事件(false);
//此标志用于增加在服务停止时生成线程正常完成的机会。
私有布尔停止请求{get;set;}
私有int_executingTasksCount;
私有int ExecutingTasksCount{get{return\u ExecutingTasksCount;}}
私有无效incCurrentTaskScont()
{
联锁增量(参考执行taskscount);
}
私有void DecCurrentTaskScont()
{
联锁减量(参考执行taskscount);
}
公共任务调度程序()
{
初始化组件();
Thread spawningThread=新线程(dospawantaskexecutionthreads);
spawningThread.Name=“spawningThread”;
spawingthread.IsBackground=false;
spawingthread.Start();
}
启动时受保护的覆盖无效(字符串[]args)
{
}
受保护的覆盖void OnStop()
{
StopRequested=true;
}
私有void dospawantaskexecutionthreads()
{
//我们检查StopRequested,尝试在服务停止时优雅地完成此线程。
而(!StopRequested)
{
而(!StopRequested&&ExecutingTaskCount0)
{
Thread.Sleep(200);//每秒检查五次。
}
_WriteEntry(“生成线程与任务执行线程一起完成”);
}
私有void ExecuteTask(对象状态)
{
尝试
{
任务任务=(任务)状态;
task.Execute();
}
抓住
{
//处理异常。
}
最后
{
DecCurrentTaskScont();
_finishedTaskAutoResetEvent.Set();
}
}
}我在这里看到一个问题: StopRequested不应是自动属性。您应该将其定义为带有支持字段的属性,以便对其进行标记 如果没有这一点,当退出条件由服务设置时,您的线程可能看不到(至少马上看不到)
另外,如果.NET 4是一个选项,那么可以使用
CancellationToken
和BlockingCollection
使用Join方法“优雅地”杀死线程。有一些关于该方法的信息。我发现代码有几个问题
的检查不是线程安全的StopRequested
的检查不是线程安全的ExecutingTaskCount
- 由于
是一个\u finishedTaskAutoResetEvent
信号可能会丢失,因为自动重置事件
不保持计数。也许这就是您想要的,但它可能会导致嵌套的等待句柄
循环发生奇怪的旋转while
CountdownEvent
类
public class TaskScheduler : ServiceBase
{
private m_Stop as ManualResetEvent = new ManualResetEvent(false);
protected override void OnStart(string[] args)
{
var thread = new Thread(DoSpawnTaskExecutionThreads);
thread.Name = "Spawning Thread";
thread.IsBackground = false;
thread.Start();
}
protected override OnStop()
{
m_Stop.Set();
}
public DoSpawnTaskExecutionThreads()
{
// The semaphore will control how many concurrent tasks can run.
var pool = new Semaphore(MaxPooledThreads, MaxPooledThreads);
// The countdown event will be used to wait for any pending tasks.
// Initialize the count to 1 so that we treat this thread as if it
// were a work item. This is necessary to avoid a subtle race
// with a real work item that completes quickly.
var tasks = new CountdownEvent(1);
// This array will be used to control the spinning of the loop.
var all = new WaitHandle[] { pool, m_Stop };
while (WaitHandle.WaitAny(all) == 0)
{
// Indicate that there is another task.
tasks.AddCount();
// Queue the task.
Thread.QueueUserWorkItem(
(state) =>
{
try
{
var task = (Task)state;
task.Execute();
}
finally
{
pool.Release(); // Allow another task to be queued.
tasks.Signal(); // Indicate that this task is complete.
}
}, new Task());
}
// Indicate that the main thread is complete.
tasks.Signal();
// Wait for all pending tasks.
tasks.Wait();
}
}
这在这里不起作用-您不能阻止服务线程,否则服务主机将强制终止它,因为关闭服务时有一个超时。我唯一要更改StopRequested in的位置是OnStop()。谢谢您的建议。@Den:OnStop将从单独的线程调用。如果没有这一点,TaskScheduler的线程将不会(必要地)看到变化了。@Den:基本上,JIT可能会看到,
dospawantaskexecutionreads
从不更改StopRequested
,因此它可能会尝试通过提升重复读取并将其组合到循环外部和上方的一个来优化重复读取。循环中的WaitOne
调用将阻止编译器在实际中进行优化,但这是一种意外情况。在这里,最好遵循里德的建议,这样就不会有任何疑问。感谢您提供如此详尽的解释和示例代码。但是我有一些问题:1)“ExecutingTaskCount的检查不是线程安全的”:为什么?我只是使用联锁类修改它。如果出于某种原因我仍然想使用它,我会怎么做呢?您建议何时使用联锁类?2) “..FinishedTaskaAutoResetEvent是一个自动重置事件,由于WaitHandle未维护计数,信号可能会丢失…”:这种情况的原因可能是什么?任务引发未处理的异常,而我由于某种原因没有处理它?RE#1…要使ExecutingTasksCount
线程安全,您必须对\u ExecutingTasksCount
进行易失性读取。这可以使用Interlocated.CompareeExchange
方法或将变量标记为volatile
。RE#2…想象一个假设场景(这是不太可能的),其中所有任务线程在DecCurrentTaskScont
和FinishedTaskKautoResetEvent.Set
之间被抢占。我认为您的嵌套循环可以防止任何问题,但我正在设想它们可以以奇怪的方式运行。同样,我认为这种方法实际上没有任何问题,但很难去思考。
private volatile bool stopRequested;
private bool StopRequested
{
get { return this.stopRequested; }
set { this.stopRequested = value; }
}
public class TaskScheduler : ServiceBase
{
private m_Stop as ManualResetEvent = new ManualResetEvent(false);
protected override void OnStart(string[] args)
{
var thread = new Thread(DoSpawnTaskExecutionThreads);
thread.Name = "Spawning Thread";
thread.IsBackground = false;
thread.Start();
}
protected override OnStop()
{
m_Stop.Set();
}
public DoSpawnTaskExecutionThreads()
{
// The semaphore will control how many concurrent tasks can run.
var pool = new Semaphore(MaxPooledThreads, MaxPooledThreads);
// The countdown event will be used to wait for any pending tasks.
// Initialize the count to 1 so that we treat this thread as if it
// were a work item. This is necessary to avoid a subtle race
// with a real work item that completes quickly.
var tasks = new CountdownEvent(1);
// This array will be used to control the spinning of the loop.
var all = new WaitHandle[] { pool, m_Stop };
while (WaitHandle.WaitAny(all) == 0)
{
// Indicate that there is another task.
tasks.AddCount();
// Queue the task.
Thread.QueueUserWorkItem(
(state) =>
{
try
{
var task = (Task)state;
task.Execute();
}
finally
{
pool.Release(); // Allow another task to be queued.
tasks.Signal(); // Indicate that this task is complete.
}
}, new Task());
}
// Indicate that the main thread is complete.
tasks.Signal();
// Wait for all pending tasks.
tasks.Wait();
}
}