Let’s check the source. Task.Run
basically calls Task.InternalStartNew
with a bunch of default arguments. This is how that method works:
internal static Task InternalStartNew(
Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler,
TaskCreationOptions options, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark)
{
// Validate arguments.
if (scheduler == null)
{
throw new ArgumentNullException("scheduler");
}
Contract.EndContractBlock();
// Create and schedule the task. This throws an InvalidOperationException if already shut down.
// Here we add the InternalTaskOptions.QueuedByRuntime to the internalOptions, so that TaskConstructorCore can skip the cancellation token registration
Task t = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);
t.PossiblyCaptureContext(ref stackMark);
t.ScheduleAndStart(false);
return t;
}
As you can see, it creates the task and then eventually starts it. It however does a bit more like scheduling it properly, and ensuring the context. So it’s probably a very good idea to use Task.Run
whenever you can to avoid having to do all of this manually. But essentially they do “the same” thing, just in different depths.