6
\$\begingroup\$

I am implementing a music player for Windows 10, and I am in a bit of a pickle at the library phase.

I am scanning for all the files in a music library (plus optional folders the user selects) and I need to get their tags (only need the title and artist; don't need complex details).

The problem is performance. Getting the music properties cost a LOT if you have a large media library. At the moment I am doing the following:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    Task.Factory.StartNew(async () =>
    {
        try
        {
            var files = await StorageHelper.GetFiles(KnownFolders.MusicLibrary);
            List<LocalAudio> tempList = new List<LocalAudio>();
            foreach (var file in files)
            {
                MusicProperties properties = await file.Properties.GetMusicPropertiesAsync();

                var title = string.IsNullOrWhiteSpace(properties.Title) ? file.Name : properties.Title;
                var artist = string.IsNullOrWhiteSpace(properties.Artist) ? file.Path : properties.Artist;
                tempList.Add(new LocalAudio() { Title = title, FilePath = file.Path, Artist = artist, Duration = properties.Duration });
                if (tempList.Count > 50)
                {
                    await AddToLibrary(tempList);
                    tempList.Clear();
                }
            }
            await AddToLibrary(tempList);
            tempList.Clear();
        }
        catch (Exception ex)
        {
            //log exception here
        }
    });
}

private async Task AddToLibrary(List<LocalAudio> tempList)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        foreach (var item in tempList)
        {
            this.LocalFiles.Add(item);
        }
    });
}

Where LocalAudio is my audio model and this.LocalFiles is an ObservableCollection. Also, the StorageHelper.GetFiles(StorageFolder) method returns an IEnumerable of StorageFiles (music files) (it's a recursive method to go in depth).

I am adding batches of 50 files on the dispatcher thread so I won't block the UI (I know, even if it is an async method running in a task that I started by fire-and-forget it still blocks the UI if I add them continuously...).

How can I optimize this better? Any idea is good. I was thinking to scan them once and save the music properties (title, artist and duration) in a file and read that file on application start and just sync the filename with the data I have in that file (a dictionary or something and read the file and keep it in memory). Not sure how good this idea is.

\$\endgroup\$
2

1 Answer 1

2
\$\begingroup\$
Task.Factory.StartNew(async () =>

I don't see any expensive non-async operations here, so I would consider directly running this code on the UI thread. It would also mean that you don't have to use Dispatcher.RunAsync in AddToLibrary.

Getting the properties will stay asynchronous, so it shouldn't block the UI.


List<LocalAudio> tempList = new List<LocalAudio>();

You can use var here, since the type of the variable is clear.


All async methods should be named ending with Async, in your case, that means AddToLibraryAsync.


I don't know if this is going to help, but you might consider getting the music properties in parallel. For example (using Batch from MoreLINQ or your own implementation):

var files = await StorageHelper.GetFiles(KnownFolders.MusicLibrary);

foreach (var batch in files.Batch(50))
{
    IEnumerable<Task<LocalAudio>> propertiesTasks = batch.Select(async file => 
    {
        MusicProperties properties = await file.Properties.GetMusicPropertiesAsync();

        var title = string.IsNullOrWhiteSpace(properties.Title) ? file.Name : properties.Title;
        var artist = string.IsNullOrWhiteSpace(properties.Artist) ? file.Path : properties.Artist;
        return new LocalAudio { Title = title, FilePath = file.Path, Artist = artist, Duration = properties.Duration };
    });

    LocalAudio[] properties = await Task.WhenAll(propertiesTasks);

    AddToLibrary(properties); // I'm assuming AddToLibrary is no longer async
}

You seem to be worried that adding the data to your UI will take too long. The solution to that is to not show the user so much data. Some solutions to that are only showing one "page" of data at a time or using virtualization to not create more UI elements than necessary (I have no idea if or how well does virtualization work on UPW).

Another possibility is to use paging, virtualization or just showing only a limited number of items, and combine it with some form of filtering. That way, when a user is looking for a song, they for example can type its name, instead of scrolling though a long list.

\$\endgroup\$
2
  • \$\begingroup\$ Wow, this is very helpful. Thank you. Didn't know about that MoreLINQ library, i'll definetly try it. \$\endgroup\$
    – Darksody
    Commented Jun 10, 2016 at 16:28
  • \$\begingroup\$ Anyway, not sure if running the getproperties method in parallel will do any good, since the I/O speed is the same. But i'll experiment on it \$\endgroup\$
    – Darksody
    Commented Jun 10, 2016 at 16:29

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.