我们托管一个高容量的WCF Web服务,该服务在逻辑上具有以下代码:
void WcfApiMethod()
{
// logic
// invoke other tasks which are critical
var mainTask = Task.Factory.StartNew(() => { /* important task */ });
mainTask.Wait();
// invoke background task which is not critical
var backgroundTask = Task.Factory.StartNew(() => { /* some low-priority background action (not entirely async) */ });
// no need to wait, as this task is best effort. Fire and forget
// other logic
}
// other APIs
现在,在某些情况下,低优先级后台任务可能会花费更长的时间(〜30秒),例如,检测到SQL连接问题,DB性能问题,Redis缓存问题等,这些都会使这些背景线程延迟,这意味着由于高容量,“总待处理任务数”将增加。
这会创建一个方案,在该方案中,较新的API执行无法安排高优先级任务,因为有许多后台任务在排队。
将TaskCreationOptions.LongRunning添加到高优先级任务将立即执行它。但是,这对我们来说不是解决方案,因为在系统中到处都有很多任务被调用,因此我们无法使它们在任何地方都可以长时间运行。同样,WCF对传入API的处理将依赖于.NET线程池,该线程池现在处于饥饿状态。
通过信号量创建短路的低优先级后台任务。仅在系统具有处理线程的能力时才产生线程(检查先前创建的线程是否已退出)。如果没有,那就不要产生线程。例如,由于问题(例如DB perf问题),IO等待中有约10,000个后台线程(非异步),这可能会导致主.net线程池中的线程不足。在这种特定情况下,我们可以添加一个信号量以将创建限制为100个,因此,如果卡住了100个任务,则首先不会创建第101个任务。
是否有一种方法可以在“自定义线程/线程池”而不是默认的.NET线程池上专门生成“任务”。这是针对我提到的后台任务,因此,如果它们延迟了,它们不会使整个系统崩溃。可能会被覆盖并创建一个自定义TaskScheduler传递给Task.Factory.StartNew(),因此,创建的任务将不在默认的.NET线程池上,而是在其他一些自定义池上。
ConcurrentExclusiveSchedulerPair.ConcurrentScheduler
可用于实现此目的。 对于上述情况,以下代码限制了后台线程破坏应用程序/防止线程匮乏。
{
// fire and forget background task
var task = Task.Factory.StartNew(
() =>
{
// background threads
}
, CancellationToken.None
, TaskCreationOptions.None
, concurrentSchedulerPair.ConcurrentScheduler);
}
private static ConcurrentExclusiveSchedulerPair concurrentSchedulerPair = new ConcurrentExclusiveSchedulerPair(
TaskScheduler.Default,
maxConcurrencyLevel: 100);
关于使用TaskScheduler.Default和maxConcurrencyLevel:100参数的注意事项,例如,您使用此limited-conc-scheduler创建10000个任务,并尝试立即使用'default-scheduler'产生另一个线程,除非所有创建了100个线程。如果尝试使用maxConcurrencyLevel:10,则立即实例化所有10个线程后,将立即生成新的线程且不会阻塞。感谢@Theodor Zoulias提供的指针。