在单独的线程池中执行某些后台任务,以避免对主线程中执行的关键任务造成饥饿 [英] Execute certain background Tasks in separate ThreadPool to avoid starvation to critical Tasks executed in main thread

查看:157
本文介绍了在单独的线程池中执行某些后台任务,以避免对主线程中执行的关键任务造成饥饿的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在单独的线程池中执行某些后台任务(不是线程),以避免对主线程中执行的关键任务(不是线程)造成饥饿

我们的方案

我们托管了一个大容量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连接问题、数据库性能问题、Redis缓存问题等,这会导致这些后台线程延迟,这意味着由于数据量大,挂起任务总数将会增加。

这会造成这样一种情况,即API的较新执行不能调度高优先级任务,因为许多后台任务都在队列中。

我们尝试的解决方案

  1. 将TaskCreationOptions.LongRunning添加到高优先级任务将立即执行。 然而,这不是我们的解决方案,因为系统中到处都在调用很多任务,我们不能让它们在每个地方都长时间运行。 此外,WCF对传入API的处理将依赖于.NET线程池,而该线程池现在正处于匮乏状态。

  2. 通过信号量创建短路低主后台任务。仅在系统有能力处理线程时才派生线程(检查以前创建的线程是否已退出)。如果不是,那就不要产生线程。 例如,由于一个问题(比如数据库性能问题),大约有10,000个后台线程(非异步)处于IO等待状态,这可能会导致.NET主线程池中的线程不足。 在这个特定的例子中,我们可以添加一个信号量来将创建限制为100个,因此如果100个任务停滞,第101个任务一开始就不会被创建。

    /li>

询问替代解决方案

有没有一种方法可以专门在"自定义线程/线程池"上生成"任务",而不是默认的.NET线程池。 这是我提到的后台任务,所以万一它们延迟了,它们不会拖累整个系统。 可以覆盖并创建要传递到Task.Factory.StartNew()中自定义TaskScheduler,因此,创建的任务不会位于默认的.NET线程池中,而是位于某些其他自定义池中。

推荐答案

这里有一个静电RunLowPriority方法,您可以用它来代替Task.Run。它具有针对简单任务和泛型任务以及针对普通委托和异步委托的重载。

const int LOW_PRIORITY_CONCURRENCY_LEVEL = 2;
static TaskScheduler LowPriorityScheduler = new ConcurrentExclusiveSchedulerPair(
    TaskScheduler.Default, LOW_PRIORITY_CONCURRENCY_LEVEL).ConcurrentScheduler;

public static Task RunLowPriority(Action action,
    CancellationToken cancellationToken = default)
{
    return Task.Factory.StartNew(action, cancellationToken,
        TaskCreationOptions.DenyChildAttach, LowPriorityScheduler);
}

public static Task RunLowPriority(Func<Task> function,
    CancellationToken cancellationToken = default)
{
    return Task.Factory.StartNew(function, cancellationToken,
        TaskCreationOptions.DenyChildAttach, LowPriorityScheduler).Unwrap();
}

public static Task<TResult> RunLowPriority<TResult>(Func<TResult> function,
    CancellationToken cancellationToken = default)
{
    return Task.Factory.StartNew(function, cancellationToken,
        TaskCreationOptions.DenyChildAttach, LowPriorityScheduler);
}

public static Task<TResult> RunLowPriority<TResult>(Func<Task<TResult>> function,
    CancellationToken cancellationToken = default)
{
    return Task.Factory.StartNew(function, cancellationToken,
        TaskCreationOptions.DenyChildAttach, LowPriorityScheduler).Unwrap();
}

通过RunLowPriority方法计划的操作将在ThreadPool线程上运行,但所有可用ThreadPool线程中最多可以有2个线程并发分配给RunLowPriority任务。

请记住,将SynchronizingObject属性设置为nullElapsed事件也在ThreadPool线程中运行。因此,如果您在此处理程序内执行低优先级工作,则可能应该通过相同的有限并发调度器进行调度:

var timer = new System.Timers.Timer();
timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
{
    Thread.Sleep(10); // High priority code
    var fireAndForget = RunLowPriority(() =>
    {
        if (!timer.Enabled) return;
        Thread.Sleep(1000); // Simulate long running code that has low priority
    });
};

这篇关于在单独的线程池中执行某些后台任务,以避免对主线程中执行的关键任务造成饥饿的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆