Search code examples
c#multithreadingperformanceparallel.foreach

Parallel.ForEach use case


I am wondering if I should be using Parallel.ForEach() for my case. For a bit of context: I am developing a small music player using the NAudio library. I want to use Parallel.ForEach() in a factory method to quickly access .mp3 files and create TrackModel objects to represent them (about 400). The code looks like this:

public static List<TrackModel> CreateTracks(string[] files)
{
    // Guard Clause
    if (files == null || files.Length == 0) throw new ArgumentException();

    var output = new List<TrackModel>();

    TrackModel track;

    Parallel.ForEach(files, file =>
    {
        using (MusicPlayer musicPlayer = new MusicPlayer(file, 0f))
        {
             track = new TrackModel()
             {
                 FilePath = file,
                 Title = File.Create(file).Tag.Title,
                 Artist = File.Create(file).Tag.FirstPerformer,
                 TrackLength = musicPlayer.GetLengthInSeconds(),
             };
         }

         lock (output)
         {
             output.Add(track);
         }
   });

   return output;
}

Note: I use lock to prevent multiple Threads from adding elements to the list at the same time.

My question is the following: Should I be using Parallel.ForEach() in this situation or am I better off writing a normal foreach loop? Is this the right approach to achieve better performance and should I be using multithreading in combination with file access in the first place?


Solution

  • You're better off avoiding both a foreach and Parallel.ForEach. In this case AsParallel() is your friend.

    Try this:

    public static List<TrackModel> CreateTracks(string[] files)
    {
        if (files == null || files.Length == 0) throw new ArgumentException();
    
        return
            files
                .AsParallel()
                .AsOrdered()
                .WithDegreeOfParallelism(2)
                .Select(file =>
                {
                    using (MusicPlayer musicPlayer = new MusicPlayer(file, 0f))
                    {
                        return new TrackModel()
                        {
                            FilePath = file,
                            Title = File.Create(file).Tag.Title,
                            Artist = File.Create(file).Tag.FirstPerformer,
                            TrackLength = musicPlayer.GetLengthInSeconds(),
                        };
                    }
                })
                .ToList();
    }
    

    This handles all the parallel logic and the locking behind the scenes for you.