How to get a second System.Thread.ThreadPool?

2019-02-23 11:03发布

问题:

If I use the ThreadPool in a nested way, my application hangs:

 ThreadPool.QueueUserWorkItem((state) =>
      ThreadPool.QueueUserWorkItem(Action));

How to get a second and independent ThreadPool to achieve nesting?

回答1:

There is only one single ThreadPool - it's not something you can (or should) make more than one instance of in an application.

I don't recommend doing this, but if you really wanted to, you could use multiple instances of your own ThreadPool implementation, such as SmartThreadPool. This would, technically, allow separate "thread pools".

However, I suspect you're hanging due to a deadlock, not due to the ThreadPool usage. I would investigate where you're getting the hangs. The VS2010 concurrency visualizer is very nice for this, if you have a copy of the VS 2010 beta installed.



回答2:

Your application is probably hanging because you are filling it up to the point where all of the active threads are waiting on queued threads. (I assume the original thread is waiting for the work item(s) it queues to complete.)

The ThreadPool has by default 25 * (number of CPUs) worker threads (IIRC).

You probably want to rework the way you're queuing items. If you're queuing them up and then finishing and exiting the thread, you're fine, but having work items hanging around waiting on other processes is generally a bad design; if that's what you're doing you probably need to use real threads or a completely different design. Using real threads is probably an equally bad idea because all that will do (based on what I can surmise of your usage) you'll just create a large number of threads, which will do nothing good for your performance.

A better design might be to have some kind of queue or stack that a few (2-4 depending on number of CPUs) worker threads add items to and pop items off to work. If an item needs to queue a new item it just adds it to the stack (and adds itself back to the stack with some kind of dependency tracking if it needs to wait for the other item).



回答3:

You are using the wrong tools.

In .NET 4.0 they introduced the Task Parallel Library. This allows you to do things like use multiple thead pools as well as have parent-child relationships between work items.

Start with the Task class, which replaces ThreadPool.QueueUserWorkItem.

http://msdn.microsoft.com/en-us/library/system.threading.tasks.task(VS.100).aspx

EDIT

Example of creating your own thread pool using Task and TaskScheduler.

http://msdn.microsoft.com/en-us/library/dd997413(VS.100).aspx



回答4:

There is one and only one threadpool.



回答5:

While the Windows API allows for multiple Thread Pools in a process, .NET does not directly expose this, however:

There is exactly one .NET ThreadPool per .NET AppDomain, and absolutely no way to change this.

So with different application domains.. it is possible to have different .NET ThreadPools within the same process.

(NOTE: The heuristics for the Hill Climb algorithm used in .NET is also determined on a per-.NET ThreadPool basis. This, along with being able to specify a maximum number of workers per type, is a downside of a 'global per appdomain' ThreadPool with varied workloads.)



回答6:

One way is using BlockingCollection. This is the class i build for it:

Updated at 2018-04-23:

public class WorkerPool<T> : IDisposable
{
    BlockingCollection<T> queue = new BlockingCollection<T>();
    List<Task> taskList;
    private CancellationTokenSource cancellationToken;
    int maxWorkers;
    private bool wasShutDown;

    int waitingUnits;

    public WorkerPool(CancellationTokenSource cancellationToken, int maxWorkers)
    {
        this.cancellationToken = cancellationToken;
        this.maxWorkers = maxWorkers;
        this.taskList = new List<Task>();
    }
    public void enqueue(T value)
    {
        queue.Add(value);
        waitingUnits++;
    }
    //call to signal that there are no more item
    public void CompleteAdding()
    {
        queue.CompleteAdding();          
    }

    //create workers and put then running
    public void startWorkers(Action<T> worker)
    {
        for (int i = 0; i < maxWorkers; i++)
        {
            taskList.Add(new Task(() =>
            {
                string myname = "worker " + Guid.NewGuid().ToString();

                try
                {
                    while (!cancellationToken.IsCancellationRequested)
                    {                     
                        var value = queue.Take();
                        waitingUnits--;
                        worker(value);
                    }
                }
                catch (Exception ex) when (ex is InvalidOperationException)  //throw when collection is closed with  CompleteAdding method. No pretty way to do this.
                {
                    //do nothing
                }
            }));
        }

        foreach (var task in taskList)
        {
            task.Start();
        }
    }

    //wait for all workers to be finish their jobs
    public void await()
    {
        while (waitingUnits >0 || !queue.IsAddingCompleted)
            Thread.Sleep(100);

        shutdown();
    }

    private void shutdown()
    {
        wasShutDown = true;
        Task.WaitAll(taskList.ToArray());            
    }

    //case something bad happen dismiss all pending work
    public void Dispose()
    {
        if (!wasShutDown)
        {
            queue.CompleteAdding();
            shutdown();
        }
    }
}

To see it working build this unit test:

[TestMethod]
public void workerPoolTest()
{
    WorkerPool<int> workerPool = new WorkerPool<int>(new CancellationTokenSource(), 5);

    workerPool.startWorkers(value =>
    {
        log.Debug(value);
    });

    for (int i = 0; i < 100; i++)
    {
        workerPool.enqueue(i);
    }
    workerPool.CompleteAdding();
    workerPool.await();
}

Now you can have as many polls has you like, just by creating new WorkPool objects.

Note that the code is not fully tested.