创建线程队列

时间:2013-11-28 18:07:49

标签: c# multithreading

我需要逐个处理用户请求(类似于队列作业)

这就是我所拥有的:

Thread checkQueue;
Boolean IsComplete = true;

protected void Start()
{
    checkQueue = new Thread(CheckQueueState);
    checkQueue.Start();    
}

private void CheckQueueState()
    {
        while (true)
        {
            if (checkIsComplete)
            {
                ContinueDoSomething();

                checkQueue.Abort();
                break;
            }
            System.Threading.Thread.Sleep(1000);
        }
    }

protected void ContinueDoSomething()
{
    IsComplete = false;
    ...
    ...
    IsComplete = true; //when done, set it to true
}

每当有来自用户的新请求时,系统将调用Start()函数并检查上一个作业是否完成,如果是,则继续下一个作业。

但我不确定以这种方式做是否正确。

有任何改进或更好的方法吗?

3 个答案:

答案 0 :(得分:1)

我喜欢usr关于使用TPL Dataflow的建议。如果您能够将外部依赖项添加到项目中(TPL Dataflow不作为.NET框架的一部分分发),那么它将为您的问题提供一个干净的解决方案。

但是,如果你坚持使用框架所提供的内容,那么你应该看一下BlockingCollection<T>,它可以很好地处理你试图实现的生产者 - 消费者模式。

我将一个快速的.NET 4.0示例放在一起,以说明如何在您的场景中使用它。它不是很精简,因为它有很多调用Console.WriteLine()。但是,如果你把所有杂乱的东西都搞得一团糟,那就太简单了。

它的中心是BlockingCollection<Action>,它从任何线程中添加了Action个委托,以及一个专门用于在{0}中顺序出列并执行这些Action的线程。它们的确切顺序。

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleProducerConsumer
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main thread id is {0}.", Thread.CurrentThread.ManagedThreadId);

            using (var blockingCollection = new BlockingCollection<Action>())
            {
                // Start our processing loop.
                var actionLoop = new Thread(() =>
                {
                    Console.WriteLine(
                        "Starting action loop on thread {0} (dedicated action loop thread).",
                        Thread.CurrentThread.ManagedThreadId,
                        Thread.CurrentThread.IsThreadPoolThread);

                    // Dequeue actions as they become available.
                    foreach (var action in blockingCollection.GetConsumingEnumerable())
                    {
                        // Invoke the action synchronously
                        // on the "actionLoop" thread.
                        action();
                    }

                    Console.WriteLine("Action loop terminating.");
                });

                actionLoop.Start();

                // Enqueue some work.
                Console.WriteLine("Enqueueing action 1 from thread {0} (main thread).", Thread.CurrentThread.ManagedThreadId);
                blockingCollection.Add(() => SimulateWork(1));

                Console.WriteLine("Enqueueing action 2 from thread {0} (main thread).", Thread.CurrentThread.ManagedThreadId);
                blockingCollection.Add(() => SimulateWork(2));

                // Let's enqueue it from another thread just for fun.
                var enqueueTask = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine(
                        "Enqueueing action 3 from thread {0} (task executing on a thread pool thread).",
                        Thread.CurrentThread.ManagedThreadId);

                    blockingCollection.Add(() => SimulateWork(3));
                });

                // We have to wait for the task to complete
                // because otherwise we'll end up calling
                // CompleteAdding before our background task
                // has had the chance to enqueue action #3.
                enqueueTask.Wait();

                // Tell our loop (and, consequently, the "actionLoop" thread)
                // to terminate when it's done processing pending actions.
                blockingCollection.CompleteAdding();

                Console.WriteLine("Done enqueueing work. Waiting for the loop to complete.");

                // Block until the "actionLoop" thread terminates.
                actionLoop.Join();

                Console.WriteLine("Done. Press Enter to quit.");
                Console.ReadLine();
            }
        }

        private static void SimulateWork(int actionNo)
        {
            Thread.Sleep(500);
            Console.WriteLine("Finished processing action {0} on thread {1} (dedicated action loop thread).", actionNo, Thread.CurrentThread.ManagedThreadId);
        }
    }
}

输出是:

0.016s: Main thread id is 10.
0.025s: Enqueueing action 1 from thread 10 (main thread).
0.026s: Enqueueing action 2 from thread 10 (main thread).
0.027s: Starting action loop on thread 11 (dedicated action loop thread).
0.028s: Enqueueing action 3 from thread 6 (task executing on a thread pool thread).
0.028s: Done enqueueing work. Waiting for the loop to complete.
0.527s: Finished processing action 1 on thread 11 (dedicated action loop thread).
1.028s: Finished processing action 2 on thread 11 (dedicated action loop thread).
1.529s: Finished processing action 3 on thread 11 (dedicated action loop thread).
1.530s: Action loop terminating.
1.532s: Done. Press Enter to quit.

答案 1 :(得分:0)

使用TPL Dataflow库中的ActionBlock<T>。将其MaxDegreeOfParalellism设置为1并完成。

请注意,ASP.NET工作进程可以随时回收(例如,由于计划的回收,内存限制,服务器重新启动或部署),因此排队的工作可能会突然丢失,恕不另行通知。我建议您查看一些外部排队解决方案,如MSMQ(或其他),以获得可靠的队列。

答案 2 :(得分:0)

查看Microsoft的Reactive Extensions。该库包含一组可用的调度程序,它们遵循您需要的语义。

最适合您需求的是EventLoopScheduler。它将排队行动并一个接一个地执行它们。如果它完成一个动作并且队列中有更多项,它将按顺序处理同一线程上的动作,直到队列为空,然后它处理该线程。当新操作排队时,它会创建一个新线程。因此,它非常有效。

代码非常简单,如下所示:

var scheduler = new System.Reactive.Concurrency.EventLoopScheduler();

scheduler.Schedule(() => { /* action here */ });

如果您需要在新线程上执行每个排队操作,请使用它:

var scheduler = new System.Reactive.Concurrency.NewThreadScheduler();

scheduler.Schedule(() => { /* action here */ });

很简单。