I am just now getting started with the task parallel library. The task in question is to process results in as parallel a manner as possible, but maintain the ordering of the results.
Also, an item can be added at any time until the flag is set indicating that no more items will be accepted.
Also, some clients will need to be notified once all results are completed (which will only occur once no more items are being accepted).
I have come up with the below simplified sample, which seems to work well in all my testing.
class Program
{
static void Main(string[] args)
{
for (int i = 1; i < random.Next(5, 21); ++i)
{
AddItem(i);
}
finishedAddingItems = true;
completion.Task.Wait();
Console.WriteLine("Finished");
Console.ReadKey();
}
static TaskCompletionSource<bool> completion =
new TaskCompletionSource<bool>();
static bool finishedAddingItems = false;
static Random random = new Random();
class QueueResult
{
public int data;
public int IsFinished;
}
static ConcurrentQueue<QueueResult> queue =
new ConcurrentQueue<QueueResult>();
static object orderingLockObject = new object();
static void AddItem(int i)
{
var queueItem = new QueueResult { data = i, IsFinished = 0 };
queue.Enqueue(queueItem);
Task.Factory
.StartNew(() =>
{
for (int busy = 0;
busy <= random.Next(9000000, 90000000);
++busy)
{ };
Interlocked.Increment(ref queueItem.IsFinished);
})
.ContinueWith(t =>
{
QueueResult result;
//the if check outside the lock is to avoid tying up resources
//needlessly, since only one continuation can actually process
//the queue at a time.
if (queue.TryPeek(out result)
&& result.IsFinished == 1)
{
lock (orderingLockObject)
{
while (queue.TryPeek(out result)
&& result.IsFinished == 1)
{
Console.WriteLine(result.data);
queue.TryDequeue(out result);
}
if (finishedAddingItems && queue.Count == 0)
{
completion.SetResult(true);
}
}
}
});
}
}
However, I am having trouble convincing myself whether or not there is a potential race condition where it is possible that an item would fail to get processed?
I think that your code may not work correctly, because you didn’t declare
IsFinishedasvolatileand you’re accessing it directly outside of a lock. In any case, using double-checked locking correctly is hard, so you shouldn’t do it unless you really have to.Also, your code is also quite a mess (having everything in one class, using
intinstead ofbool, unnecessaryContinueWith(), …) and contains at least one more thread-safety issue (Randomis not thread-safe).Because of all that, I suggest you learn about the more advanced parts of TPL. In your case, PLINQ sounds like the right solution: