C# 使用具有优先级的多线程的作业调度器

C# 使用具有优先级的多线程的作业调度器,c#,multithreading,scheduled-tasks,scheduling,scheduler,C#,Multithreading,Scheduled Tasks,Scheduling,Scheduler,我即将启动一个C#.NET 4.0项目,该项目将创建一个作业调度器 作业没有截止日期,并且可能运行时间很长,最长可达天 该工作有3个优先级:空闲、正常、关键;从最低到最高 正在不断创建新的工作 具有较高优先级的较新作业应优先于优先级较低的作业,即使旧作业已创建很长时间 每个作业都将由一个长时间运行的线程处理 工作是重新进入的。作业的状态将持久化到数据库中,因此可以随时暂停作业或终止作业线程 我的计划是使用信号量,并将并发条目的数量设置为系统核心的数量。将为队列中的每个作业创建一个新线程,并且所有

我即将启动一个C#.NET 4.0项目,该项目将创建一个作业调度器

  • 作业没有截止日期,并且可能运行时间很长,最长可达天
  • 该工作有3个优先级:空闲、正常、关键;从最低到最高
  • 正在不断创建新的工作
  • 具有较高优先级的较新作业应优先于优先级较低的作业,即使旧作业已创建很长时间
  • 每个作业都将由一个长时间运行的线程处理
  • 工作是重新进入的。作业的状态将持久化到数据库中,因此可以随时暂停作业或终止作业线程
  • 我的计划是使用信号量,并将并发条目的数量设置为系统核心的数量。将为队列中的每个作业创建一个新线程,并且所有线程都将在开始时被信号量阻塞

    我的问题是,当信号量调用release()方法时,确保高优先级线程首先进入信号量。可行吗

    我的第二个问题是,当更高优先级的作业线程存在时,让信号量内的线程退出,并让退出的作业线程返回线程队列等待信号量。可行吗

    对于这两个问题,信号量是正确的方法吗?如果不是,你有什么建议

    对于这两个问题,信号量是正确的方法吗?如果不是,你有什么建议

    这要看情况而定。通常,最好每个线程有多个作业,因为许多(特别是长时间运行的)工作项将花费时间等待CPU以外的东西。例如,如果您在工作中使用WCF服务或其他相关问题,您可能会花费大量时间被阻塞和闲置

    在这种情况下,最好让您的作业根据需要进行调度。在这种情况下,使用线程池可能会更好

    但是,如果作业都是高CPU的,那么您的方法可能是可取的。优先级队列可用于跟踪调度优先级,并决定运行哪个作业


    也就是说,我可能不会用信号灯来做这个。虽然它可以工作,但单个计数器(通过联锁的.Increment/decreation管理)和ManualResetEvent也可以工作,而且重量要轻得多。

    好吧,我更倾向于下面这样的东西

    首先,启动所需的所有线程:

    for(int i=0; i < Environment.ProcessorCount; i++)
    {
        Thread t = new Thread(RunWork);
        // setup thread
        t.Start();
        threads.Add(t);
    }
    
    然后创建一个队列管理对象。这显然会变得更复杂,因为它可能需要与您的数据库同步,等等

    class MyQueue<TJob> where TJob : ITask 
    {
        Queue<TJob> high, med, low;
        bool GetNextJob(ref TJob work)
        {
            if(work.Priority == PriorityType.High && !work.Complete)
                return true;
            lock(this)
            {
                if(high.Count > 0)
                {
                    Enqueue(work);//requeue to pick back up later
                    work = high.Dequeue();
                    return true;
                }
                if(work.Priority == PriorityType.Med && !work.Complete)
                    return true;
                if(med.Count > 0)
                {
                    Enqueue(work);//requeue to pick back up later
                    work = med.Dequeue();
                    return true;
                }
                if(!work.Complete)
                    return true;
                if(low.Count > 0)
                {
                    work = low.Dequeue();
                    return true;
                }
                work = null;
                return false;
            }
    
        void Enqueue(TJob work)
        {
            if(work.Complete) return;
            lock(this)
            {
                else if(work.Priority == PriorityType.High) high.Enqueue(work);
                else if(work.Priority == PriorityType.Med) med.Enqueue(work);
                else low.Enqueue(work);
            }
        }
    }
    

    有一个很棒的框架从Java移植到了C#看看吧


    嗯,我的调度方案完全基于优先级,某些作业需要根据新作业的到达而停止。quartz.net能帮我吗?不确定是否停止正在运行的作业,但quartz.net确实具有基于优先级的处理。在我看来,使用信号量似乎是任务并行库实现的信号。TPL和信号量不是很有效吗?第三方物流给你很大的控制权。高优先级作业将排队(例如,索引0)向下推低优先级作业。此时,TPL将允许您暂停最低优先级的作业。。。???或者我误解了什么…@IAbstract:TPL可能非常有用,尽管它没有任何内置的“暂停”,而且语义电话的使用实际上与TPL是分开的……对,有很多实现细节遗漏了……我不确定TPL中是否真的有暂停。但是,我确信可以保持作业的状态,本质上是暂停任务。:)这太棒了,不知怎的,我觉得这可能就是我想要的。你能告诉我什么是数据类型吗?我没有看到WorkReadyHandle在MyQueue类的任何地方定义。我遗漏了什么吗?一般来说,我不熟悉C。关机是一个
    线程
    手动重置事件
    ——很可能是一个线程。我仍然更喜欢TPL而不是手动线程管理,尤其是因为引用了.Net 4。TPL对长时间运行的线程是否有效,这可能会持续几天?我预计_shutdown和workready handle是手动重置事件,我可能会将shutdown和workready句柄都放在MyQueue类中,然后直接访问它们。是否需要一次关闭一个线程取决于您。
    class MyQueue<TJob> where TJob : ITask 
    {
        Queue<TJob> high, med, low;
        bool GetNextJob(ref TJob work)
        {
            if(work.Priority == PriorityType.High && !work.Complete)
                return true;
            lock(this)
            {
                if(high.Count > 0)
                {
                    Enqueue(work);//requeue to pick back up later
                    work = high.Dequeue();
                    return true;
                }
                if(work.Priority == PriorityType.Med && !work.Complete)
                    return true;
                if(med.Count > 0)
                {
                    Enqueue(work);//requeue to pick back up later
                    work = med.Dequeue();
                    return true;
                }
                if(!work.Complete)
                    return true;
                if(low.Count > 0)
                {
                    work = low.Dequeue();
                    return true;
                }
                work = null;
                return false;
            }
    
        void Enqueue(TJob work)
        {
            if(work.Complete) return;
            lock(this)
            {
                else if(work.Priority == PriorityType.High) high.Enqueue(work);
                else if(work.Priority == PriorityType.Med) med.Enqueue(work);
                else low.Enqueue(work);
            }
        }
    }
    
    public void RunWork()
    {
        ITask job;
        while(!_shutdown.WaitOne(0))
        {
            if(queue.GetNextJob(ref job))
                job.PerformOneUnitOfWork();
            else
                WaitHandle.WaitAny(new WaitHandle[] { _shutdown, queue.WorkReadyHandle });
        }
    }