Question

Let's suppose I have this piece of code.

foreach(string value in myList)
  {
      string result = TimeConsumingTask(value);
      //update UI
      listBox.Add(result);
  }

I want to move string result = TimeConsumingTask(value); to some background thread. What is the simplest yet efficient way of moving this to background thread if

- Order does matter
- Order doesn't matter
Was it helpful?

Solution

A simple way to guarantee order is to do all the TimeConsumingTasks sequentially in the same background thread.

This may take slightly longer than executing multiple TimeConsumingTasks on multiple threads, but saves you a lot of headache trying to synchronize the results and produce output in the required order.

If your main objective is to free up the UI so the user can continue working on something else, the difference in completion time between using one thread and multiple threads is probably negligible, so go the simplest route - use a single background thread for all the TimeConsumingTasks.

If your main objective is to make the computation of the TimeConsumingTasks complete more quickly, then running multiple threads is probably the better approach since it is more likely to take advantage of multiple cores and execute some of the work in parallel.

To preserve order in the multiple threads case, you will have to order the results yourself. You can either maintain an accumulator temp list and insert each result into it at the result's appropriate location in the list (if the location / index is easily computed or known), or you add an additional processing step after all the threads have completed to coalesce the multiple results into the final output in the desired order. In parallel and distributed computing circles, the process of splitting a problem into multiple chunks to be completed in parallel and then combining the results into a coherent order is called "map reduction".

OTHER TIPS

In a GUI application such as WPF, WinForms, Silverlight, ... you could use a BackgroundWorker as it takes care of marshaling the different callbacks that occur on the main thread which is important when updating the UI. In ASP.NET you may take a look at asynchronous pages.

Let's take an example if the order does matter with a background worker:

var worker = new BackgroundWorker();
worker.DoWork += (obj, args) =>
{
    args.Result = myList.Select(TimeConsumingTask).ToList();
};
worker.RunWorkerCompleted += (obj, args) =>
{
    var result = (IList<string>)args.Result;
    foreach(string value in result)
    {
        listBox.Add(result);
    }
}; 
worker.RunWorkerAsync();

There are many ways to skin this cat. The past few years I've used many, BackgroundWorker being the most common and straightforward. However from now on, you've got to think the Async CTP is the way to go. At least review the intro before deciding. In terms of simplicity and clean, concise code it is some of the hottest code to ever be written. :)

I would use a Task. That way the entire operation runs in the background without blocking the UI. Be sure, though, to update the list box on the main thread. I'm not sure how to do that in WinForms, but in WPF you accomplish it using Dispatcher.Invoke() or Dispatcher.BeginInvoke() like below.

If ordering matters:

Task.Factory.StartNew(
    () =>
        {
            foreach (string value in myList)
            {
                string result = TimeConsumingTask(value);
                //update UI
                Application.Current.Dispatcher.BeginInvoke(
                    (Action)(() => listBox.Add(result)));
            }            
        });

If ordering doesn't matter you could also use Parallel.ForEach().

Task.Factory.StartNew(
    () =>
        {
            Parallel.ForEach(myList, 
                value =>
                {
                    string result = TimeConsumingTask(value);
                    //update UI
                    Application.Current.Dispatcher.BeginInvoke(
                       (Action)(() => listBox.Add(result)));
                });            
        });
    }

You have to wait for the result before you can update the UI, so there's no sense in moving only the TimeConsumingTask() to the background thread. You will need to update the UI from the background thread (but marshalling the call to the UI thread).

It depends on your ultimate goal, but one simple solution is just to kick off a task. This will work if order does matter.

Task.Factory.StartNew(
    () => 
    {
        foreach(string value in myList)   
        {       
            string result = TimeConsumingTask(value);       
            listBox.Invoke(new Action(() => listBox.Add(result)));   
        } 
    });

Using BackgroundWorker is also pretty easy, but not as "simple", since there is more code:

  • create a BackgroundWorker
  • assign a DoWork handler
  • assign a Progress handler
  • set WorkerReportsProgress
  • call RunWorkerAsync()

If order doesn't matter, you can use the Parallel.ForEach loop. (Edited according to dthorpe and ttymatty's comments).

using System.Threading.Tasks;

var results = new List<string>();

Parallel.ForEach(myList, value =>
{
    string result = TimeConsumingTask(value);
    results.Add(result);
});

//update UI
listBox.AddRange(results);

If you want to callback the UI with process information, I'd recommend using a BackgroundWorker as Darin Dimitrov recommends in his answer.

There is a big difference between multithreading and background processing, though you can use them both at the same time.

With background processing -- using BackgroundWorker as Darin suggests -- you can allow the user to continue to work in the UI without waiting for the background process to complete. If the user has to wait for the processing to finish before they can proceed, then there's no point in running it in the background.

With multithreading -- using Parallel Extensions Library, perhaps -- you can use multiple threads to run the repetitive loop in parallel so that it completes faster. This would be beneficial if your user is waiting for the process to finish before they can continue.

Finally, if you are running something in the background, you can get it to finish faster by using multithreading.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top