【问题标题】:different behavior between Factory.StartNew and Task.Run?Factory.StartNew 和 Task.Run 之间的不同行为?
【发布时间】:2018-05-07 17:23:04
【问题描述】:

我试图了解 Factory.StartNew 和 Task.Run 之间的区别。我在here等各个地方都看到了等价性。

我想我必须在我的情况下使用 Factory.StartNew(),因为我想插入我自己的 TaskScheduler。

所以总结起来,似乎是:

Task.Run(action)

严格等价于:

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

但是,我用一个简单的 SerialQueue 进行了一些测试,抓取了 from Microsoft's samples for Parallel Programming with the .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)
任务 0 - 开始工作 1 秒 (ManagedThreadId=5 IsThreadPoolThread=True)
Task0 - Zzz 1st 1sec (ManagedThreadId=5 IsThreadPoolThread=True)
任务 0 - 完成 (ManagedThreadId=5 IsThreadPoolThread=True)
任务 1 - 开始工作 1 秒 (ManagedThreadId=5 IsThreadPoolThread=True)
Task1 - Zzz 1st 1sec (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)
任务 0 - 开始工作 1 秒 (ManagedThreadId=5 IsThreadPoolThread=True)
Task0 - Zzz 1st 1sec (ManagedThreadId=5 IsThreadPoolThread=True)
任务 1 - 开始工作 1 秒 (ManagedThreadId=5 IsThreadPoolThread=True) 什么?
Task1 - Zzz 1st 1sec (ManagedThreadId=5 IsThreadPoolThread=True)
任务 0 - 完成 (ManagedThreadId=9 IsThreadPoolThread=True)
任务 1 - 完成(ManagedThreadId=5 IsThreadPoolThread=True)

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

【问题讨论】:

    标签: c# .net task taskscheduler


    【解决方案1】:

    任务工厂的返回类型是Task &lt;Task&gt;,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&lt;Task&gt;。如果您在 Task.Run 中将委托声明为 Func&lt;object&gt;,您将在 Task.Run 中看到相同的行为。

    【讨论】:

    • 顺便说一句:StartNew 是否有任何理由不支持 Func 设计?
    • @Christophe 因为只需要解包任务的能力,因为 async/await 已添加到 .net 并且 StartNew 比这更旧。
    猜你喜欢
    • 2013-01-21
    • 2020-12-05
    • 2014-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多