Factory.StartNew 和 Task.Run 之间的行为有何不同?

问题描述 投票:0回答:1

我试图理解 Factory.StartNew 和 Task.Run 之间的区别。我在不同的地方看到了等价物,比如here

我认为在我的例子中我必须使用 Factory.StartNew() 因为我想插入我自己的 TaskScheduler。

总结一下,似乎是:

Task.Run(action)

严格等同于:

Task.Factory.StartNew(action, 
    CancellationToken.None, 
    TaskCreationOptions.DenyChildAttach, 
    TaskScheduler.Default);

但是,我使用一个简单的 SerialQueue 进行了一些测试,该 SerialQueue 是从 Microsoft 的 .NET Framework 并行编程示例中获取的。 这是简单的代码:

/// <summary>Represents a queue of tasks to be started and executed serially.</summary> public class SerialTaskQueue { /// <summary>The ordered queue of tasks to be executed. Also serves as a lock protecting all shared state.</summary> private Queue<object> _tasks = new Queue<object>(); /// <summary>The task currently executing, or null if there is none.</summary> private Task _taskInFlight; /// <summary>Enqueues the task to be processed serially and in order.</summary> /// <param name="taskGenerator">The function that generates a non-started task.</param> public void Enqueue(Func<Task> taskGenerator) { EnqueueInternal(taskGenerator); } /// <summary>Enqueues the task to be processed serially and in order.</summary> /// <param name="taskOrFunction">The task or functino that generates a task.</param> /// <remarks>The task must not be started and must only be started by this instance.</remarks> private void EnqueueInternal(object taskOrFunction) { // Validate the task if (taskOrFunction == null) throw new ArgumentNullException("task"); lock (_tasks) { // If there is currently no task in flight, we'll start this one if (_taskInFlight == null) StartTask_CallUnderLock(taskOrFunction); // Otherwise, just queue the task to be started later else _tasks.Enqueue(taskOrFunction); } } /// <summary>Starts the provided task (or function that returns a task).</summary> /// <param name="nextItem">The next task or function that returns a task.</param> private void StartTask_CallUnderLock(object nextItem) { Task next = nextItem as Task; if (next == null) next = ((Func<Task>)nextItem)(); if (next.Status == TaskStatus.Created) next.Start(); _taskInFlight = next; next.ContinueWith(OnTaskCompletion); } /// <summary>Called when a Task completes to potentially start the next in the queue.</summary> /// <param name="ignored">The task that completed.</param> private void OnTaskCompletion(Task ignored) { lock (_tasks) { // The task completed, so nothing is currently in flight. // If there are any tasks in the queue, start the next one. _taskInFlight = null; if (_tasks.Count > 0) StartTask_CallUnderLock(_tasks.Dequeue()); } } }

现在这是我的一些模拟组合任务的代码(包括等待/继续)。

public static async Task SimulateTaskSequence(int taskId) { Console.WriteLine("Task{0} - Start working 1sec (ManagedThreadId={1} IsThreadPoolThread={2})", taskId, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); Thread.Sleep(200); Console.WriteLine("Task{0} - Zzz 1st 1sec (ManagedThreadId={1} IsThreadPoolThread={2})", taskId, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); await Task.Delay(200); Console.WriteLine("Task{0} - Done (ManagedThreadId={1} IsThreadPoolThread={2})", taskId, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); }

Test1:

使用带有Task.Run()的队列: static void Main(string[] args) { Console.WriteLine($"Starting test program (ManagedThreadId={Thread.CurrentThread.ManagedThreadId} IsThreadPoolThread={Thread.CurrentThread.IsThreadPoolThread})"); SerialTaskQueue co_pQueue = new SerialTaskQueue(); for (int i = 0; i < 2; i++) { var local = i; co_pQueue.Enqueue(() => Task.Run(() => { return SimulateTaskSequence(local); })); } }

结果是正确的,队列按照预期的顺序进行处理(在切换到Task1之前实现Task0)。

启动测试程序(ManagedThreadId=1 IsThreadPoolThread=False)
Task0 - 开始工作 1 秒 (ManagedThreadId=5 IsThreadPoolThread=True)

任务0 - Zzz 第 1 1 秒(ManagedThreadId=5 IsThreadPoolThread=True)
任务 0 - 完成(ManagedThreadId=5 IsThreadPoolThread=True)
任务1 - 开始工作 1 秒 (ManagedThreadId=5 IsThreadPoolThread=True)
任务1 - Zzz 第 1 秒(ManagedThreadId=5 IsThreadPoolThread=True)
任务 1 - 完成 (ManagedThreadId=8 IsThreadPoolThread=True)

测试 2:

仅使用 Factory.StartNew 及其完美等价物: static void Main(string[] args) { Console.WriteLine($"Starting test program (ManagedThreadId={Thread.CurrentThread.ManagedThreadId} IsThreadPoolThread={Thread.CurrentThread.IsThreadPoolThread})"); SerialTaskQueue co_pQueue = new SerialTaskQueue(); for (int i = 0; i < 2; i++) { var local = i; co_pQueue.Enqueue(() => Task.Factory.StartNew(() => { return SimulateTaskSequence(local); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default)); } }

但是这次我得到以下输出:

启动测试程序(ManagedThreadId=1 IsThreadPoolThread=False)
Task0 - 开始工作 1 秒 (ManagedThreadId=5 IsThreadPoolThread=True)

任务0 - Zzz 第 1 1 秒(ManagedThreadId=5 IsThreadPoolThread=True)
任务 1 - 开始工作 1 秒 (ManagedThreadId=5 IsThreadPoolThread=True)
什么?
任务1 - Zzz 第 1 秒(ManagedThreadId=5 IsThreadPoolThread=True) 任务 0 - 完成(ManagedThreadId=9 IsThreadPoolThread=True)
任务 1 - 完成 (ManagedThreadId=5 IsThreadPoolThread=True)

我不明白其中的区别。为什么行为不同?我以为是等价的?! (记住,接下来的步骤是插入我自己的调度程序)

c# .net task scheduled-tasks
1个回答
2
投票
Task <Task>

,Task.Run的返回类型只是

Task

您需要使用工厂解开内部任务,以便队列代码中的

ConinueWith

在内部任务而不是外部任务上运行延续。


static void Main(string[] args) { Console.WriteLine($"Starting test program (ManagedThreadId={Thread.CurrentThread.ManagedThreadId} IsThreadPoolThread={Thread.CurrentThread.IsThreadPoolThread})"); SerialTaskQueue co_pQueue = new SerialTaskQueue(); for (int i = 0; i < 2; i++) { var local = i; co_pQueue.Enqueue(() => Task.Factory.StartNew(() => { return SimulateTaskSequence(local); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap()); } }

Task.Run 有一个重载,它接受为您执行此操作的 
Func<Task>

。如果您在 Task.Run 中将委托声明为

Func<object>
,您会在 Task.Run 中看到相同的行为。
    

© www.soinside.com 2019 - 2024. All rights reserved.