Search code examples
c#recursionprogress

Measure progress in recursive search


I have a recursive search, which searches for files with a specified name in a whole directory. This needs quite some time and I want to show a progressbar, which shows atleast some progress (I don't care if the progress is just measured by the number of files or directory or the actual size of them).

Following (recursive code) performs the search:

private void searchFolder(PortableDeviceFolder parent, ref List<PortableDeviceFolder> result, string filename)
{
    foreach (var item in parent.Files)
    {

        if (item is PortableDeviceFolder)
        {
            searchFolder((PortableDeviceFolder)item, ref result, filenames);
        }
        else if ((String.Compare(item.Name, filename)==0))
        {
            result.Add(parent);
            Console.WriteLine("Found file in: " + parent.Name);
        }    
    }
}

I couldn't figure out how to get the progress. I was thinking about an easy algorithm which measures the same percentage for each file and folder;

  • Documents (10%)
    • privat(10%/3)
    • work(10%/3)
      • project1 (10%/3/2)
      • project2 (10%/3/2)
    • familly (10%/3)
  • ...

and so on.

Unfortunately I wasn't able to implement this in the recursive search. Has anybody the time to give me an example? Thanks in advance


Solution

  • Commenter TaW's suggestion that you can count the directories to use as the basis for progress is good. But there's a small problem, not with the suggestion itself but in the way your code seems to be implemented.

    It's not clear from your question what PortableDeviceFolder is. But it appears to abstract the difference between a file and a folder, returning both from the Files property. Given that the most time-consuming aspect of your processing is likely the actual retrieval of the file names for a given directory, then if the only mechanism PortableDeviceFolder has to return the directories in a given directory is that Files property, you'd have to wind up enumerating all the files along with the directories, ignoring the files returned for the purpose of generating the count.

    In other words, getting the count will take almost as long as the actual search for the given name.

    So, for the purpose of this answer I will assume that the PortableDeviceFolder class has another property Folders which returns just the directories themselves. With such a property, then we can take advantage of the suggestion TaW's offered.

    First, you'll need to get that count. A method to do that would look like this:

    private int CountFolders(PortableDeviceFolder rootFolder)
    {
        return rootFolder.Folders.Select(folder => CountFolders(folder)).Sum() + 1;
    }
    

    There will be a slight delay before any progress is shown, because of course there's no useful way to predict how long the above will take. But it should be relatively brief, since we are looking only at the directories and not all the files.

    To be able to update the progress while the processing is going, we should run the search in a separate thread and use Dispatcher.Invoke() to update the ProgressBar.Value property. The search method then looks like this:

    private void SearchFolder(PortableDeviceFolder parent,
        List<PortableDeviceFolder> result, string fileName, IProgress<int> progress)
    {
        foreach (var item in parent.Files)
        {
            PortableDeviceFolder folder = item as PortableDeviceFolder;
    
            if (folder != null)
            {
                SearchFolder(folder, result, fileName, progress);
            }
            else if (item.Name.Equals(fileName, StringComparison.OrdinalIgnoreCase))
            {
                result.Add(parent);
            }
        }
    
        progress.Report(1);
    }
    

    NOTE: it's not clear to me why you were passing the result parameter by-reference. That doesn't seem to be needed, and so I've change it to a regular by-value parameter.

    In your UI code, you might call it like this:

    private async void SearchFolder_Click(object sender, RoutedEventArgs e)
    {
        Button button = (Button)sender;
    
        button.IsEnabled = false;
    
        string searchPath = textBlock1.Text, searchText = textBox1.Text;
        List<PortableDeviceFolder> folders = new List<PortableDeviceFolder>();
        PortableDeviceFolder rootFolder = new WindowsDirectoryFolder(searchPath);
    
        progressBar1.Value = 0;
        progressBar1.Maximum = await Task.Run(() => CountFolders(rootFolder));
    
        Progress<int> progress =
            new Progress<int>(increment => progressBar1.Value += increment);
    
        await Task.Run(() => SearchFolder(rootFolder, folders, searchText, progress));
    
        listBox1.ItemsSource = folders;
        button.IsEnabled = true;
    }
    

    Note that here we execute both the CountFolders() and the SearchFolder() method asynchronously, to ensure the UI remains responsive while the work is being done.

    The above is from a simple WPF program I wrote to demonstrate the technique, but it can be adapted to Winforms or other GUI frameworks without difficulty. The basic idea remains the same.