views:

904

answers:

3

Here's a relatively common task for me, and, I think, for many a .NET programmer:
I want to use the .NET ThreadPool for scheduling worker threads that need to process a given type of tasks.

As a refresher, the signatures for the queueing method of the ThreadPool and its associated delegate are:

public static bool QueueUserWorkItem (
    WaitCallback callBack,
    Object state
)
public delegate void WaitCallback (Object state)

Therefore, a typical generic worker thread class would look something like:

public class Worker<T> {
    public void schedule(T i_task) {
        ThreadPool.QueueUserWorkItem(execute, i_task)
    }
    private void execute(Object o){
        T task = (T)o;  //What happened to the type safety?  
        executeTask(task);
    }
    private void executeTask(T i_task){
        //process i_task
    }
}

Notice the type of the state parameter? It's Object !

What's the compelling reason why the .NET team chose not to make the QueueUserWorkItem method (or the whole ThreadPool class) generic? I can't believe they just overlooked it.

Here's how I'd like to see it:

//in the ThreadPool class:
public static bool QueueUserWorkItem<T> (
    WaitCallback<T> callBack,
    T state
)
public delegate void WaitCallback<T> (T state)

This would make the worker class type-safe (and a lot clearer, IMHO):

public class Worker<T> {
    public void schedule(T i_task) {
        ThreadPool.QueueUserWorkItem<T>(execute, i_task)
    }
    private void execute(T i_task){
        //process i_task
    }
}

I must be missing something.

+6  A: 

Since it's trivial to package whatever state you like by passing an anonymous delegate or lambda to the threadpool (through variable capture), there's no need for a generic version.

For example, you could write a utility function:

static void QueueItem<T>(Action<T> action, T state)
{
    ThreadPool.QueueUserWorkItem(delegate { action(state); });
}

But it wouldn't be terribly useful, as you can simply use a delegate yourself any time you need state in the pooled task.

Barry Kelly
+4  A: 

It sounds like you are talking about a work queue? (and I sound like clippy...)

For the record, thread-pool threads should typically be used for short pieces of work. You should ideally create your own threads for a long-lived queue. Note that .NET 4.0 may be adopting the CCR/TPL libraries, so we'll get some inbuilt work queues for free - but it isn't hard to write a threaded work-queue. And you can make it generic, too ;-p

Re the question - I prefer the captured variables approach to passing state into threads (be they Thread, ThreadPool, or Control.Invoke):

    Thread t = new Thread(() => SomeMethod(arg));
    t.IsBackground = true;
    t.Name = "Worker n";
    t.Start();

This gives you much more granular control over the thread, without saturating the ThreadPool.

Marc Gravell
I don't understand your argument of not using a thread pool as the underlying 'execution' layer of a work queue. Is this a problem when using *the* .NET ThreadPool class due to the limited number of threads there? IMO it's natural to assume tasks in a work queue are short. Can you please elaborate?
Cristi Diaconescu
+1  A: 

ThreadPool exists since .NET 1.1 which didn't have Generics.

I like how they chose not to break backwards compatibility :-)

Alvaro Rodriguez
I don't think this is a valid argument. Just look at all the container classes in 1.1 that got "upgraded" to use generics in 2.0. Plus I was not suggesting to *replace* the Queue... method with it's generic counterpart, but to add the generic version alongside it.
Cristi Diaconescu