107

I'm trying to run multiple functions that connect to a remote site (by network) and return a generic list. But I want to run them simultaneously.

For example:

public static List<SearchResult> Search(string title)
{
    //Initialize a new temp list to hold all search results
    List<SearchResult> results = new List<SearchResult>();

    //Loop all providers simultaneously
    Parallel.ForEach(Providers, currentProvider =>
    {
        List<SearchResult> tmpResults = currentProvider.SearchTitle((title));

        //Add results from current provider
        results.AddRange(tmpResults);
    });

    //Return all combined results
    return results;
}

As I see it, multiple insertions to 'results' may happend at the same time... Which may crash my application.

How can I avoid this?

2
  • Which .NET version are you using? Commented Nov 3, 2011 at 20:57
  • 4
    It would have to be at least .Net 4; Parallel was introduced there. Commented Nov 3, 2011 at 20:58

5 Answers 5

178

You can use a concurrent collection.

The System.Collections.Concurrent namespace provides several thread-safe collection classes that should be used in place of the corresponding types in the System.Collections and System.Collections.Generic namespaces whenever multiple threads are accessing the collection concurrently.

You could for example use ConcurrentBag since you have no guarantee which order the items will be added.

Represents a thread-safe, unordered collection of objects.

Sign up to request clarification or add additional context in comments.

2 Comments

Yes, this is the actual answer. You'll get better performance (generally) with concurrent collections.
A ConcurrentQueue<T> might be preferable to a ConcurrentBag<T>, because the former preserves the insertion order. The ConcurrentBag<T> is an extremely specialized collection intended for mixed producer consumer scenarios, and adding items in parallel (without taking items from the collection) is not such a scenario.
74
//In the class scope:
Object lockMe = new Object();    

//In the function
lock (lockMe)
{    
     results.AddRange(tmpResults);
}

Basically a lock means that only one thread can have access to that critical section at the same time.

7 Comments

But what will happend if WHILE those results are being added the results from another provider try to add to? will they FAIL or WAIT until possible?
When there's a lock, the thread will wait until it can get the lock.
A minor point: this is not the safest choice for the lock object. Better to use a special private object: lock(resultsLock) .
locks can slow down the overall execution time though.. concurrent collections seem better to avoid that
Why do we need a lock at class level if the object modified is declared locally ? If we lock(results) and then add to it what is wrong ? Lock at class level means, if multiple requests are calling Search then you have degraded the performance
|
44

For those who prefer code:

public static ConcurrentBag<SearchResult> Search(string title)
{
    var results = new ConcurrentBag<SearchResult>();
    Parallel.ForEach(Providers, currentProvider =>
    {
        results.Add(currentProvider.SearchTitle((title)));
    });

    return results;
}

1 Comment

Have to use a loop: foreach (var item in currentProvider.SearchTitle((title))) results.Add(item);
27

The Concurrent Collections are new for .Net 4; they are designed to work with the new parallel functionality.

See Concurrent Collections in the .NET Framework 4:

Before .NET 4, you had to provide your own synchronization mechanisms if multiple threads might be accessing a single shared collection. You had to lock the collection ...

... the [new] classes and interfaces in System.Collections.Concurrent [added in .NET 4] provide a consistent implementation for [...] multi-threaded programming problems involving shared data across threads.

Comments

17

This could be expressed concisely using PLINQ's AsParallel and SelectMany:

public static List<SearchResult> Search(string title)
{
    return Providers.AsParallel()
                    .SelectMany(p => p.SearchTitle(title))
                    .ToList();
}

2 Comments

linq selectMany is great, sadly linq is slower than normal foreach. :(
Don't micro-optimize. The OP implied that SearchTitle connects to a remote site. Its latency will be several orders of magnitude slower than the difference between LINQ and foreach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.