All parallel tasks are finished in multiple threads, that means, thread is a basic unit for parallel tasks.
So, I think thread pool is more efficient than TPL.
WHY?
BECAUSE TPL's default task scheduler is ThreadPoolTaskScheduler:
private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTaskScheduler();
and let's see ThreadPoolTaskScheduler:
protected internal override void QueueTask(Task task)
{
if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)
{
new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork)
{
IsBackground = true
}.Start(task);
return;
}
bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) != TaskCreationOptions.None;
ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
}
Then, let's see threadpool
:
internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
{
ThreadPool.EnsureVMInitialized();
try
{
}
finally
{
ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
}
}
OK...let's see our other choice:
public static bool UnsafeQueueUserWorkItem(WaitCallback callBack, object state)
{
StackCrawlMark stackCrawlMark = StackCrawlMark.LookForMyCaller;
return ThreadPool.QueueUserWorkItemHelper(callBack, state, ref stackCrawlMark, false);
}
OK..let's dig more:
private static bool QueueUserWorkItemHelper(WaitCallback callBack, object state, ref StackCrawlMark stackMark, bool compressStack)
{
bool result = true;
if (callBack != null)
{
ThreadPool.EnsureVMInitialized();
try
{
return result;
}
finally
{
QueueUserWorkItemCallback callback = new QueueUserWorkItemCallback(callBack, state, compressStack, ref stackMark);
ThreadPoolGlobals.workQueue.Enqueue(callback, true);
result = true;
}
}
throw new ArgumentNullException("WaitCallback");
}
Now, finally the same point we found.
So, which is better, it's your choice.
That's why I never use TPL
but instead user threadpool
directly.