问题
I've read some of the MSDN documentation for the Task Parallel Library (http://msdn.microsoft.com/en-us/library/dd537609(v=vs.110).aspx) specifically about the best practice usage of the TPL.
I have an application which starts up a thread. The purpose of the thread is to monitor a queue and "process" items which have been added. The processing of the items on the queue needs to be done sequentially and so I am not looking to processes multiple items at once form the queue. The thread lives as long as the windows process and is only shutdown on application exit.
I would like to know the pros and cons of starting this background thread as a task using the TPL.
My initial gut instinct is not to use the TPL as I would be taking up a thread pool thread for the entire life of the running application, which could potentially stop other tasks from running and interfere with the optimal running of the thread pool.
I'm not sure how, starting up a new background thread manually to perform the work, would affect any usage of the TPL in other isolated parts of the application.
I would like to know wehther a manual thread or a task is the recommended approach in this scenario? Or what other considerations I would need to take into account to make an informed choice?
I've included the code below. Please note that the "processors" in the code usually do CPU bound operations and then the "handlers" usually do IO bound operations:
public class TransactionProcessor : IDisposable
{
private readonly IProcessorConfiguration configuration;
private readonly IList<Tuple<string, IProcessor>> processors = new List<Tuple<string, IProcessor>>();
private readonly IList<Tuple<string, IHandler>> handlers = new List<Tuple<string, IHandler>>();
private AutoResetEvent waitForWork = new AutoResetEvent(true);
private object lockObject = new object();
private bool processThreadShouldExit = false;
private Thread processorThread;
private Queue queue = new Queue();
public TransactionProcessor(IProcessorConfiguration configuration)
{
if (configuration == null)
{
throw new ArgumentNullException("configuration");
}
this.configuration = configuration;
this.Initialise();
}
public void Start()
{
lock (this.lockObject)
{
if (this.processorThread == null)
{
this.processThreadShouldExit = false;
this.processorThread = new Thread(this.Dispatcher);
this.processorThread.Start();
}
}
}
public void Stop()
{
if (this.processorThread != null)
{
this.processThreadShouldExit = true;
this.waitForWork.Set();
this.processorThread.Join();
this.processorThread = null;
}
}
public void QueueTransactionForProcessing(Transaction Transaction, Guid clientID)
{
var queueObject = new QueueObject() { Transaction = Transaction };
lock (this.lockObject)
{
this.queue.Enqueue(queueObject);
}
if (this.waitForWork != null)
{
this.waitForWork.Set();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (this.processorThread != null)
{
this.Stop();
}
if (this.waitForWork != null)
{
this.waitForWork.Dispose();
this.waitForWork = null;
}
}
private void Dispatcher()
{
if (this.queue.Count == 0)
{
this.waitForWork.Reset();
}
while (!this.processThreadShouldExit)
{
if (this.queue.Count > 0 || this.waitForWork.WaitOne(60000))
{
while (this.queue.Count > 0 && !this.processThreadShouldExit)
{
QueueObject queueObject;
lock (this.lockObject)
{
queueObject = (QueueObject)this.queue.Dequeue();
}
this.ProcessQueueItem(queueObject);
}
if (this.queue.Count == 0)
{
this.waitForWork.Reset();
}
}
}
}
private void ProcessQueueItem(QueueObject item)
{
var correlationId = Guid.NewGuid();
try
{
bool continuePipeline = true;
foreach (var processor in this.processors)
{
processor.Item2.Process(item.Transaction, correlationId, ref continuePipeline);
if (!continuePipeline)
{
break;
}
}
if (continuePipeline)
{
foreach (var handler in this.handlers)
{
Transaction clonedTransaction = item.Transaction.Clone();
try
{
handler.Item2.Handle(clonedTransaction, correlationId);
}
catch (Exception e)
{
}
}
}
}
catch (Exception e)
{
}
}
private void Initialise()
{
foreach (var processor in this.configuration.Processors)
{
try
{
Type processorType = Type.GetType(processor.Value);
if (processorType != null && typeof(IProcessor).IsAssignableFrom(processorType))
{
var processorInstance = (IProcessor)Activator.CreateInstance(processorType);
this.processors.Add(new Tuple<string, IProcessor>(processor.Key, processorInstance));
}
catch (Exception e)
{
}
}
foreach (var handler in this.configuration.Handlers)
{
try
{
Type handlerType = Type.GetType(handler.Value);
if (handlerType != null && typeof(IHandler).IsAssignableFrom(handlerType))
{
var handlerInstance = (IHandler)Activator.CreateInstance(handlerType);
this.handlers.Add(new Tuple<string, IHandler>(handler.Key, handlerInstance));
}
}
catch (Exception e)
{
}
}
}
}
回答1:
You should vary rarely need to use a low level construct such as a Thread, the TPL caters for long running tasks as well and using it results in more flexible and maintainable code.
You can start a long running task using:
Task.Factory.StartNew(() => {}, TaskCreationOptions.LongRunning);
From the MSDN doco:
LongRunning Specifies that a task will be a long-running, coarse-grained operation involving fewer, larger components than fine-grained systems. It provides a hint to the TaskScheduler that oversubscription may be warranted. Oversubscription lets you create more threads than the available number of hardware threads.
So the scheduler can create extra threads to make sure that the ThreadPool capacity is adequate. Also, manually creating a Thread will not affect your usage of the TPL in other parts of the code.
Personally, I would definitely opt for TPL over manual thread creation. There is a bit of a learning curve, but it is a very powerful library that caters for a large variety of scenarios.
来源:https://stackoverflow.com/questions/25601182/recommended-usage-of-tpl-for-very-long-living-threads