I'm trying to move some of my old projects from ThreadPool
and standalone Thread
to TPL Task
, because it supports some very handy features, like continuations with Task.ContinueWith
(and from C# 5 with async\await
), better cancellation, exception capturing, and so on. I'd love to use them in my project. However I already see potential problems, mostly with synchronization.
I've written some code which shows a Producer / Consumer problem, using a classic stand-alone Thread
:
class ThreadSynchronizationTest
{
private int CurrentNumber { get; set; }
private object Synchro { get; set; }
private Queue<int> WaitingNumbers { get; set; }
public void TestSynchronization()
{
Synchro = new object();
WaitingNumbers = new Queue<int>();
var producerThread = new Thread(RunProducer);
var consumerThread = new Thread(RunConsumer);
producerThread.Start();
consumerThread.Start();
producerThread.Join();
consumerThread.Join();
}
private int ProduceNumber()
{
CurrentNumber++;
// Long running method. Sleeping as an example
Thread.Sleep(100);
return CurrentNumber;
}
private void ConsumeNumber(int number)
{
Console.WriteLine(number);
// Long running method. Sleeping as an example
Thread.Sleep(100);
}
private void RunProducer()
{
while (true)
{
int producedNumber = ProduceNumber();
lock (Synchro)
{
WaitingNumbers.Enqueue(producedNumber);
// Notify consumer about a new number
Monitor.Pulse(Synchro);
}
}
}
private void RunConsumer()
{
while (true)
{
int numberToConsume;
lock (Synchro)
{
// Ensure we met out wait condition
while (WaitingNumbers.Count == 0)
{
// Wait for pulse
Monitor.Wait(Synchro);
}
numberToConsume = WaitingNumbers.Dequeue();
}
ConsumeNumber(numberToConsume);
}
}
}
In this example, ProduceNumber
generates a sequence of increasing integers, while ConsumeNumber
writes them to the Console
. If producing runs faster, numbers will be queued for consumption later. If consumption runs faster, the consumer will wait until a number is available. All synchronization is done using Monitor
and lock
(internally also Monitor
).
When trying to 'TPL-ify' similar code, I already see a few issues I'm not sure how to go about. If I replace new Thread().Start()
with Task.Run()
:
- TPL
Task
is an abstraction, which does not even guarantee that the code will run on a separate thread. In my example, if the producer control method runs synchronously, the infinite loop will cause the consumer to never even start. According to MSDN, providing aTaskCreationOptions.LongRunning
parameter when running the task should hint theTaskScheduler
to run the method appropriately, however I didn't find any way to ensure that it does. Supposedly TPL is smart enough to run tasks the way the programmer intended, but that just seems like a bit of magic to me. And I don't like magic in programming. - If I understand how this works correctly, a TPL
Task
is not guaranteed to resume on the same thread as it started. If it does, in this case it would try to release a lock it doesn't own while the other thread holds the lock forever, resulting in a deadlock. I remember a while ago Eric Lippert writing that it's the reason whyawait
is not allowed in alock
block. Going back to my example, I'm not even sure how to go about solving this issue.
These are the few issues that crossed my mind, although there may be (probably are) more. How should I go about solving them?
Also, this made me think, is using the classical approach of synchronizing via Monitor
, Mutex
or Semaphore
even the right way to do TPL code? Perhaps I'm missing something that I should be using instead?
Thread
rather than usingTask
still has its place for certain things. Long-running non-IO-bound jobs for example. – OlatheThreadPool
internally, one would be able to do everything you can do withThreadPool
, plus the "sugar". Hence why I decided to invest in switching over. And the problem is called producer-consumer, not provider-consumer... doh! Now I feel silly. – ChrysalisThread
is more for the long-running non-I/O stuff. Also, another really great tech for consumers is Microsoft's Reactive Extensions (RX). It can look a bit "what-tha?" at first but it is perhaps elegant. This site has some jolly good tutorials. Check them all out and pick the tech you think is suitable for you. – Olathe