Thread Pooling in .NET 4 with dictionary collection of threads, and exception handling.
   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Threading;
   6:  using System.Threading.Tasks;
   7:   
   8:  namespace ConsoleThreadTests
   9:  {
  10:      class ThreadTest9
  11:      {
  12:          static void Main()    // The Task class is in System.Threading.Tasks
  13:          {
  14:              const int MaxThreads = 10;
  15:              const int ProcessesToRun = 50;
  16:   
  17:              Dictionary<int, Task<string>> x = new Dictionary<int, Task<string>>();
  18:              int WorkIndex = 0;  // incremented, and becomes the key to the dictionary.
  19:              bool Completed = false;
  20:              while (WorkIndex < ProcessesToRun || !Completed)
  21:              {
  22:                  // retrieve any work done by completed threads.
  23:                  int t = -1;
  24:                  foreach (int t1 in x.Keys)
  25:                  {
  26:                      if (x[t1].IsCompleted || x[t1].IsCanceled || x[t1].IsFaulted)
  27:                      {
  28:                          t = t1;
  29:                          if (x[t1].IsFaulted)
  30:                          {
  31:                              // production code here would have to handle the AggregateException in the Exception property, etc.
  32:                              Console.WriteLine(string.Format("{0} on thread: {1}", x[t1].Exception.GetType().ToString(), x[t1].Exception.Message));
  33:                              Exception e = x[t1].Exception.InnerException;
  34:                              while (e != null)
  35:                              {
  36:                                  Console.WriteLine(string.Format("({0}) {1}", e.GetType().ToString(), e.Message));
  37:                                  e = e.InnerException;
  38:                              }
  39:                          }
  40:                          else if (x[t1].IsCompleted)
  41:                          {
  42:                              Console.WriteLine(x[t1].Result);
  43:                          }
  44:                          else
  45:                          {
  46:                              Console.WriteLine("CANCELLED");
  47:                          }
  48:                          break;
  49:                      }
  50:                  }
  51:                  if (t >= 0)
  52:                  {
  53:                      x.Remove(t);
  54:                      Completed = (x.Count == 0 && WorkIndex == ProcessesToRun);
  55:                  }
  56:                  if (WorkIndex == ProcessesToRun || x.Count == MaxThreads) continue;  // done with work or threads busy. Keep looping until threads complete.
  57:   
  58:                  WorkIndex++;
  59:                  int w = WorkIndex;
  60:                  string sendstr = string.Format("Cumquat-{0:0#}", w);
  61:                  Console.WriteLine(sendstr);
  62:                  x.Add(w, Task.Factory.StartNew<string>(() => Go(sendstr, w)));
  63:                  if (WorkIndex == ProcessesToRun)
  64:                  {
  65:                      Console.WriteLine (string.Format("Total Number of slots needed: {0}", x.Count));
  66:                  }
  67:              }
  68:              Console.WriteLine("Done");
  69:              Console.ReadLine();
  70:          }
  71:   
  72:          static string Go(string x, int i)
  73:          {
  74:              if (i == 20) throw new Exception("Handle this!");
  75:              string result = string.Format("{0}: {1}-- Callback", x, i % 2 == 0);
  76:              Console.WriteLine(result);
  77:              return result;
  78:          }
  79:      }
  80:  }