In a previous post I implemented a ParallelMap class using the ThreadPool class and some fancy schmancy code. My method ended up looking like this:
public static IEnumerable<TResult> ParallelMap<TArg, TResult>(
this IEnumerable<TArg> list,
Func<TArg, TResult> func)
{
List<TResult> result = new List<TResult>();
using (var resetEvent = new ManualResetEvent(false))
{
int count = 0;
foreach (TArg item in list)
{
WaitCallback run =
n =>
{
TResult value = func(item);
lock (result)
{
result.Add(value);
}
if (Interlocked.Decrement(ref count) == 0)
{
resetEvent.Set();
}
};
Interlocked.Increment(ref count);
ThreadPool.QueueUserWorkItem(run);
}
resetEvent.WaitOne();
}
return result;
}
Well, I have been wanting to play with ParallelFX for a while and after Daniel Moth commented on a blog post the other day I saw his ParallelFX videos and started watching some of them. I then began playing around with some of the code in the latest CTP and rewrote the ParallelMap method above using ParallelFX:
public static IEnumerable<TResult> ParallelMap<TArg, TResult>(
this IEnumerable<TArg> list,
Func<TArg, TResult> func)
{
List<TResult> result = new List<TResult>();
Parallel.ForEach<TArg>(list,
(TArg n) =>
{
TResult val = func(n);
lock (result)
{
result.Add(val);
}
});
return result;
}
Pretty simple. I use the same method signature as before and I declare my result list, but then all I have to do is call Parallel.ForEach and pass in my list as the first parameter along with a lambda that takes a TArg (I put the type there so that the code is clearer what is being passed in). As you can see in my lambda I am calling my method and getting the result then I add my answer to the result. I still have to lock on the result since this code is being executed in multiple threads.
In a later post I implemented a ParallelMap method that maintained order, it was even bigger than the method above and it even required a seperate struct to maintain the index and the item. So, I decided to go ahead and try to rewrite that method using the Parallel Extensions:
public static IEnumerable<TResult> ParallelMap<TArg, TResult>(
this IEnumerable<TArg> list,
Func<TArg, TResult> func)
{
TResult[] result = new TResult[list.Count()];
Parallel.ForEach<TArg>(list,
(TArg n, int i) =>
{
result[i] = func(n);
});
return result;
}
Wow. If hadn’t been writing my earlier post for fun, I would have felt like I wasted a lot of time. 🙂 I just declare my array instead of a list and then I call Parallel.ForEach and then I change my lambda to take the optional index parameter. This allows me to assign my results to my array in the order that they appeared in the original IEnumerable. Pretty awesome. I’m going to have to play around with the other parts of ParallelFX and hopefully make a few posts on it in the future. Hope you enjoyed!
Loved the article? Hated it? Didn’t even read it?
We’d love to hear from you.
Very cool, man! I’m really excited about PFX…hopefully it will be rolled into .NET 4.
I don’t think you need to lock result. Just putting the result of func(n) in to result[i] is safe.
TResult[] result = new TResult[list.Count()];
Parallel.ForEach<TArg>(list, (TArg n, int i) =>
{
result[i] = func(n);
});
return result;
Yeah, I realized this in a later post as well. Sometimes when I am doing multi-threaded programming I just go lock crazy. I guess it is better to over-do it than under-do it. 🙂
Here’s an even faster one for IList<TArg>
TResult[] result = new TResult[list.Count()];
Parallel.For(0, result.Length, i =>
{
result[i] = func(list[i]);
});
return result;