当我需要可扩展性时,我应该不使用TPL吗

本文关键字:TPL 我应该 可扩展性 | 更新日期: 2023-09-27 17:58:36

线程池线程是重要的可重用线程,(例如在asp.net中)有助于为请求提供服务。

与不使用线程池线程并且不支持取消令牌、连续性、结果值的原语new Thread().start(....)相反,毫无疑问,TPL是首选策略。

但问题是Task使用线程池线程。

在TPL中,TaskScheduler负责对任务进行实际排队等待执行。默认调度程序将使用线程池。

这让我想知道:

假设我有一个有许多并发用户的站点,我需要为每个用户执行3个计算绑定任务(非IO)。

我担心线程池将没有线程剩余(因为我将通过使用线程池线程的TASK进行3个计算操作(为每个用户)。-这将导致创建新的线程池线程。

这让我得出结论,当我需要可伸缩性时,最好使用老式的new Thread().start(...)

我错过了什么?我们回到原点了吗?

当我需要可扩展性时,我应该不使用TPL吗

您很少需要在ASP.NET中使用Task.RunTask.Factory.StartNew。这些API用于CPU绑定工作,在处理HTTP请求时将CPU绑定工作卸载到另一个线程通常没有意义(与客户端UI应用程序不同)。这只会损害可扩展性。只需在当前线程上执行操作。

如果您需要在多个HTTP请求中生成一个具有生存期的长时间运行任务,请使用@Damien_The_Unbeliever在评论中建议的单独进程a,例如,在同一主机或单独主机上的WCF服务。

也就是说,至少在当前的TPL实现中,您仍然可以将一个非池线程封装为具有所有优点的Task。它是在将TaskCreationOptions.LongRunningTask.Factory.StartNew一起使用时创建的。在这样的线程中,Thread.IsThreadPoolThread将是false。这可以防止ThreadPool饥饿,但肯定会增加进程的工作集。此外,此类线程不会像ThreadPool线程那样被TPL重用,因此重复创建非池线程的成本可能相当高。

作为TaskCreationOptions.LongRunning的替代方案,使用TaskCompletionSourcenew Thread().Start()包装为Task是很容易的,具有相同的消除、异常和结果传播逻辑。

还有一个想法是使用自定义任务调度程序,它总是在新线程上对任务进行排队:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new { Thread.CurrentThread.ManagedThreadId });
        var task = Task.Factory.StartNew(
            () => Console.WriteLine(new { 
                Thread.CurrentThread.IsThreadPoolThread,
                Thread.CurrentThread.ManagedThreadId}),
            CancellationToken.None,
            // hide the scheduler from inner tasks
            TaskCreationOptions.HideScheduler,
            NewThreadTaskScheduler.Scheduler);
        task.Wait();
    }
}
class NewThreadTaskScheduler : TaskScheduler
{
    public static readonly NewThreadTaskScheduler Scheduler = 
        new NewThreadTaskScheduler();
    NewThreadTaskScheduler()
    {
    }
    protected override void QueueTask(Task task)
    {
        var thread = new Thread(() =>
        {
            base.TryExecuteTask(task);
        });
        thread.IsBackground = true;
        thread.Start();
    }
    protected override bool TryExecuteTaskInline(
        Task task, 
        bool taskWasPreviouslyQueued)
    {
        return false;
    }
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return null;
    }
    public override int MaximumConcurrencyLevel { 
        get { return Int32.MaxValue; } }
}