title: Subsequently add tasks to a task, but limit the number of parallel executed ones
body: <p>I have severe performance problems, i.e. four cores running on 100%, blocking everything else, when I want to show thumbnails in a grid containing more than 10k entries.<br>
  There is an event that fires when a line is shown to the user, which loads the image. In this event an instance of <code>GridImageManager</code> is called with <code>AddAndStartLoading</code>. When I scroll down, this event is fired too often and my cores are flooded with threads. </p>
  
  <p>I tried to rewrite the code, using Callbacks and a list of still to load cells, but the list is not threadsafe, leading to Exceptions, when I want to remove an element, that has already been removed from another thread.</p>
  
  <p>Then I tried to use semaphores, but with them I can not realize the <code>WaitForLoadingCompleted</code>-method, because I can not access the amount of free resources.</p>
  
  <p>I also read into Tasks, but could not get how they would help me.</p>
  
  <p>My constraints are the following:</p>
  
  <ul>
  <li>Loading images must start immediately, after the first call of <code>AddAndStartLoading</code>.</li>
  <li>No more than <code>N</code> loading processes should be executed at any time.</li>
  <li>Subsequent calls to <code>AddAndStartLoading</code> may wait, but should be processed as soon as the amount of loading processes drops below <code>N</code>.</li>
  <li><code>WaitForLoadingCompleted</code> should wait for all Loading threads to be finished, while on the other hand allowing to do <code>Application.DoEvents()</code>. I can not refactor this at the moment.</li>
  </ul>
  
  <p>What patterns/strategies do I have to use to accomplish this?</p>
  
  <p>To give you an idea: This is my first attempt. It works up to the point where <code>CellsToLoad</code> gets empty.</p>
  
  <pre><code>public class GridImageManager
  {
  
      public GridImageManager()
      {
          CellsToLoad = new List&lt;(UltraGridCell cell, ImageLoader loader)&gt;();
          LoadingCells = new Dictionary&lt;UltraGridCell, ImageLoader&gt;();
      }
  
      public delegate void ImageLoader(object obj);
  
      private List&lt;(UltraGridCell cell, ImageLoader loader)&gt; CellsToLoad { get; set; }
  
      private Dictionary&lt;UltraGridCell, ImageLoader&gt; LoadingCells { get; set; }
  
      public void AddCellToLoad(UltraGridCell cell, ImageLoader loader)
      {
          CellsToLoad.Add((cell, loader));
  
          StartLoading();
      }
  
      private void CallbackImageLoaded(IAsyncResult ar)
      {
          if (ar.AsyncState is UltraGridCell cell)
          {
              LoadingCells.Remove(cell);
              StartLoading();
          }
      }
  
      private void StartLoading()
      {
          if (LoadingCells.Count &gt;= 10)
              return;
  
          if (CellsToLoad.Count == 0)
              return;
  
          var next = CellsToLoad.First();
          CellsToLoad.Remove(next);
  
          LoadingCells.Add(next.cell, next.loader);
  
          next.loader.BeginInvoke(null, CallbackImageLoaded, next.cell);
      }
  
      public void WaitForLoadingCompleted()
      {
          while (CellsToLoad.Count &gt; 0 &amp;&amp; LoadingCells.Count &gt; 0)
          {
              Application.DoEvents(); // Can't refactor this right now
          }
      }
  }
  </code></pre>