降低Task.Factory.StartNew线程的优先级。

48

像下面这样的代码将启动一个新线程来执行任务。是否有办法控制该线程的优先级?

Task.Factory.StartNew(() => {
    // everything here will be executed in a new thread.
    // I want to set the priority of this thread to BelowNormal
});
4个回答

62

正如其他人提到的那样,您需要指定一个自定义调度程序来与您的任务配合使用。不幸的是,没有合适的内置调度程序。

您可以选择Glenn链接的ParallelExtensionsExtras,但如果您想要一些简单的东西,可以直接粘贴到您的代码中,请尝试以下操作。使用方法如下:

Task.Factory.StartNew(() => {
    // everything here will be executed in a thread whose priority is BelowNormal
}, null, TaskCreationOptions.None, PriorityScheduler.BelowNormal);

代码:

public class PriorityScheduler : TaskScheduler
{
    public static PriorityScheduler AboveNormal = new PriorityScheduler(ThreadPriority.AboveNormal);
    public static PriorityScheduler BelowNormal = new PriorityScheduler(ThreadPriority.BelowNormal);
    public static PriorityScheduler Lowest = new PriorityScheduler(ThreadPriority.Lowest);

    private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();
    private Thread[] _threads;
    private ThreadPriority _priority;
    private readonly int _maximumConcurrencyLevel = Math.Max(1, Environment.ProcessorCount);

    public PriorityScheduler(ThreadPriority priority)
    {
        _priority = priority;
    }

    public override int MaximumConcurrencyLevel
    {
        get { return _maximumConcurrencyLevel; }
    }

    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return _tasks;
    }

    protected override void QueueTask(Task task)
    {
        _tasks.Add(task);

        if (_threads == null)
        {
            _threads = new Thread[_maximumConcurrencyLevel];
            for (int i = 0; i < _threads.Length; i++)
            {
                int local = i;
                _threads[i] = new Thread(() =>
                {
                    foreach (Task t in _tasks.GetConsumingEnumerable())
                        base.TryExecuteTask(t);
                });
                _threads[i].Name = $"PriorityScheduler: {i}";
                _threads[i].Priority = _priority;
                _threads[i].IsBackground = true;
                _threads[i].Start();
            }
        }
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return false; // we might not want to execute task that should schedule as high or low priority inline
    }
}

注意事项:

  • 所有的工作线程都是后台线程,因此不应使用此调度程序安排重要任务;只能安排那些在进程关闭时可以丢弃的任务
  • 改编自Bnaya Eshet的实现
  • 我并不完全理解每个覆盖方法;只是按照Bnaya的选择进行MaximumConcurrencyLevelGetScheduledTasksTryExecuteTaskInline

7
似乎现在这个项目已经在GitHub上有了一个(未署名的)位置,许可证也被更改了(可能不合法),并添加了一些用于处理最终化/处置的内容,尽管可能没有涉及AppDomain本身的关闭(IRegisteredObject)。 - Chris Moschini
2
如果你只想利用TaskParallelLibrary的易用性并设置优先级,那么这就非常详细了...不过没有冒犯之意。感谢你的回答。 - Mike de Klerk
这个解决方案似乎可行,但在重负载下会出现死锁。 - Quark Soup
@Quarkly 所有并发都由 BlockingCollection<T> 处理,这里没有花哨的多线程。你确定是这段代码死锁了,而不是你的任务? - Roman Starkov
我有500个线程在调用**_tasks.GetConsumingEnumerable()时被阻塞。如果我将PriorityScheduler.Lowest替换为TaskScheduler.Default**,我的安全价格提供程序模拟器就可以正常工作(除了它对占用CPU时间有点过于激进)。 - Quark Soup
无效链接“由Bnaya Eshet实现的实现” - Jens

20

在执行Task的实际方法中,可以设置Tasks的线程优先级。但不要忘记在完成后恢复优先级,以避免出现问题。

因此,首先启动Task:

new TaskFactory().StartNew(StartTaskMethod);

然后设置线程优先级:

void StartTaskMethod()
{
    try
    {
        // Change the thread priority to the one required.
        Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;

        // Execute the task logic.
        DoSomething();
    }
    finally
    {
        // Restore the thread default priority.
        Thread.CurrentThread.Priority = ThreadPriority.Normal;
    }
}

调整优先级的时候要记住这一点:为什么不改变ThreadPool(或Task)线程的优先级?


10
我认为这种方法很危险。这不是将优先级设置在ThreadPool线程中的一条吗?你不知道下一步该线程将用在哪里。 - Brannon
@Brannon,通过任务计划程序更改优先级可能会产生类似的影响。一般来说,您不需要更改优先级,但如果确实需要这样做,无论如何操作都没有区别。 - net_prog
2
@net_prog,我认为你不理解Brannon点。线程池中的线程是用来重复利用的。下一次你想要重复利用一个线程时,你必须确保它的优先级是正确的。默认行为是将线程设置为普通优先级。如果你使用了一个使用线程池的第三方库,并且优先级不如预期,那么你可能会遇到麻烦。 - Eric Ouellet
这是一个不好的想法,因为这些ThreadPool线程被ASP.Net本身用于服务请求。如果您有定期设置ThreadPriority的代码,并且它有时在您的代码将优先级设置回Normal之前抛出异常(或者没有代码执行此操作,就像您在这里所做的那样),则最终会滞留所有ASP.Net请求或锁定服务器,具体取决于您的操作。不要这样做!使用类似于此处最受欢迎的答案中推荐的单独的PriorityScheduler。 - Chris Moschini
5
以上评论现已大多过时,因为代码已经更新以使用try..finally。但是,它可以进一步改进,以捕获初始线程优先级,而不是将值设置回Normal。 - user2864740
1
作为一个线程可以执行不同的任务,这个线程是否可能被抢占以执行另一个任务,然后在没有原因的情况下具有更高的优先级? - Elo

12

6
我知道Thread.Priority的作用,但我想知道是否可以使用Task Factory而不是使用Thread对象来实现。 - Moon
在第一个链接中,有一段描述说使用Task Factory无法实现。不过,我从未需要更改池中线程的优先级,也不能百分之百确定这是真的。 - zerkms
我最初选择了@Roman Starkov的答案,但在负载测试后失败了。我现在倾向于这个答案:如果你想改变优先级,使用Thread而不是Task - Quark Soup

8
若需使用Task设置优先级,请查阅由微软专家Stephen Toub此 MSDN 博客文章中介绍的自定义任务调度程序。如需进一步了解,请勿错过他在第一句中提到的前两篇文章链接。
对于您的问题,看起来您可能需要查看QueuedTaskScheduler

同意@GlennSlayden的观点,您应该使用自定义TaskScheduler。我在这里使用了Microsoft提供的源代码:https://learn.microsoft.com/fr-fr/dotnet/api/system.threading.tasks.taskscheduler?view=netframework-4.8。此代码使用线程池来执行任务,但您可以处理具有不同优先级的自定义专用线程。 - Elo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接