Threadpool线程是重要的可重用线程(例如在asp.net中) - 有助于提供请求。
与不使用线程池线程的原始new Thread().start(....)
相反,并且不支持取消令牌,连续性,结果值 - 毫无疑问,TPL是首选策略。
但问题是Task
也是 uses线程池线程。
在TPL中,TaskScheduler负责实际排队任务 执行。 Default调度程序将使用线程池。
这让我想知道:
假设我有一个包含许多并发用户的站点,我需要为每个用户执行3个计算绑定任务(非IO)。
我担心线程池没有剩下的线程(因为我将通过使用Threadpool线程的TASK进行3次计算操作(对于每个用户)。这将导致创建新的线程池线程。
这让我得出结论,当我需要可扩展性时 - 使用旧时尚new Thread().start(...)
会更好吗?
我错过了什么?我们回到原点吗?
答案 0 :(得分:3)
您很少需要在ASP.NET中使用Task.Run
或Task.Factory.StartNew
。这些API用于CPU绑定工作,在处理HTTP请求时(与客户端UI应用程序不同)将CPU绑定工作卸载到另一个线程通常没有意义。这只会损害可扩展性。只需在当前线程上完成工作。
如果您需要在多个HTTP请求中生成长期运行的生命周期任务,请在注释中使用@Damien_The_Unbeliever建议的单独进程,例如,在相同或单独主机上的WCF服务。
也就是说,您仍然可以将非池线程包装为Task
所有好东西,至少在当前的TPL实现中。将TaskCreationOptions.LongRunning
与Task.Factory.StartNew
一起使用时创建的内容。在此类帖子中,Thread.IsThreadPoolThread
将为false
。这可能会阻止ThreadPool
饥饿,但肯定会增加该过程的工作集。此外,TPL不会像ThreadPool
线程那样重用这些线程,因此重复创建非池线程可能会非常昂贵。
作为TaskCreationOptions.LongRunning
的替代方案,使用new Thread().Start()
轻松将Task
包裹为TaskCompletionSource
,具有相同的取消,异常和结果传播逻辑
另一个想法是使用自定义任务调度程序,它总是在新线程上对任务进行排队:
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; } }
}