Hi,

I have a grid that is supposed to show records from a database that depict scientific papers (title, authors, year, journal, etc). The user can categorize each paper into multiple categories.

Beside the grid I now have a CheckedListbox that lists all categories. The user can check or uncheck categories, and the grid should then display only the papers that belong to the checked categories.

I would now like to load the grid asynchronously in a background thread, because it might take a while to load all papers from the database and then check them against the checked categories.

I tried two different approaches:
  1. Using a BackgroundWorker
  2. Creating a thread manually


For the BackgroundWorker, I have this code:
csharp Code:
  1. private EntityCollection<Paper> papers = new EntityCollection<Paper>();
  2.        
  3.         // Called when the user checks an item in the categories list
  4.         public override void RefreshData()
  5.         {
  6.             dataLoader.CancelAsync();
  7.             dataLoader.RunWorkerAsync();
  8.         }
  9.        
  10.         private void dataLoader_DoWork(object sender, DoWorkEventArgs e)
  11.         {
  12.             // Get the checked categories:
  13.             var categories = ViewFormObserver.Instance.MainForm.GetSelectedPaperCategories();
  14.  
  15.             // Get all papers:
  16.             var allPapers = PaperManager.Instance.Load();
  17.  
  18.             // Clear the list of papers
  19.             papers.Clear();
  20.  
  21.             // Add only those papers that have any of the checked categories
  22.             bool found = false;
  23.             papers.Clear();
  24.             foreach (var paper in allPapers)
  25.             {
  26.                 found = false;
  27.  
  28.                 foreach (var category in categories)
  29.                 {
  30.                     if (!found)
  31.                     {
  32.                         if (paper.Categories.ContainsId(category.Id))
  33.                         {
  34.                             papers.Add(paper);
  35.                             found = true;
  36.                             continue;
  37.                         }
  38.                     }
  39.                 }
  40.             }
  41.         }
  42.  
  43.         private void dataLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  44.         {
  45.             // Set the datasource of the grid
  46.             this.Grid.DataSource = papers;
  47.             this.Grid.DataBind();
  48.         }

'dataLoader' is the BackgroundWorker component.

EntityCollection<T> inherits List<T> and provides some useful methods for my database code (such as ContainsId, which checks if the collection contains a database record with the specified Id).

The PaperManager.Instance.Load() method loads all papers from the database (this is the method that probably takes the longest). Then a loop runs through every paper and every selected category and 'filters out' the papers that don't have any selected category. For a large number of papers this could also take a while.


Anyway, this works, but not if I click too quickly in the CheckedListBox. If I check an item while the grid is loading, for example I can doubleclick an item so it's checked and immediately unchecked again, then the BackgroundWorker throws an exception on RunWorkerAsync:
Code:
This BackgroundWorker is currently busy and cannot run multiple tasks concurrently.
I suppose it is not done cancelling yet thus cannot start another load operation.


So, I switched to using my own thread manually:
csharp Code:
  1. private EntityCollection<Paper> papers = new EntityCollection<Paper>();
  2.         private Thread loadingThread;
  3.        
  4.         public override void RefreshData()
  5.         {
  6.             // If we are already loading, abort
  7.             if (loadingThread != null)
  8.             {
  9.                 loadingThread.Abort();
  10.                 loadingThread = null;
  11.             }
  12.            
  13.             // Create a new thread and start it
  14.             var finishedCallback = new MethodInvoker(LoadDataFinished);
  15.             loadingThread = new Thread(LoadData);
  16.             loadingThread.IsBackground = true;
  17.             loadingThread.Start(finishedCallback);
  18.         }
  19.  
  20.         private void LoadData(object finishedCallback)
  21.         {
  22.             var callback = (MethodInvoker)finishedCallback;
  23.  
  24.             var categories = ViewFormObserver.Instance.MainForm.GetSelectedPaperCategories();
  25.             var allPapers = PaperManager.Instance.Load();
  26.  
  27.             bool found = false;
  28.             papers.Clear();
  29.             foreach (var paper in allPapers)
  30.             {
  31.                 found = false;
  32.  
  33.                 foreach (var category in categories)
  34.                 {
  35.                     if (!found)
  36.                     {
  37.                         if (paper.Categories.ContainsId(category.Id))
  38.                         {
  39.                             papers.Add(paper);
  40.                             found = true;
  41.                             continue;
  42.                         }
  43.                     }
  44.                 }
  45.             }
  46.  
  47.             callback.Invoke();
  48.         }
  49.  
  50.         private void LoadDataFinished()
  51.         {
  52.             this.Grid.DataSource = papers;
  53.             this.Grid.DataBind();
  54.         }
Code is basically the same, except instead of calling BackgroundWorker.RunWorkerAsync I create a new thread and start it. I pass a MethodInvoker to the thread which it can invoke when the loading is finished.

The problem here is that I have to Abort the thread if it's already running. As far as I'm aware you should never abort a thread unless you're application is exiting, so this seems completely wrong...

Also, sometimes it seems to grid 'crashes'. It throws an unhandled exception and draws a red cross through the control. The exception is thrown on a seemingly random method, somewhere inside the grid control (the grid is a third party grid, but that shouldn't matter I think...).

So there's definitely something wrong with my threading....



Any idea what I'm doing wrong and how I can avoid it?

In case it isn't clear, here's what I'm trying to do:

- User checks or unchecks a category in a listbox.
- Grid starts to refresh asynchronously, in the background
- If the user checks or unchecks another category while the grid is loading, the current loading operation is aborted, and it starts to load again.

There should obviously not be a 'queue' of loading operations, if the user checks 4 items in a row I want it to abort the first three loading operations and only finish the fourth, I don't want it to load the first, then load the second, then the third and finally the fourth...

Thanks!