Is there a way to stop a Directory.GetFiles search
Asked Answered
F

1

5

I'm using Directory.GetFiles() to search files in a multi-folder directory and some of these folders are too big so the search could take up to 60 seconds. In the code below a search starts a second after the user stops typing, this works great when the user types the file name and does not pauses while typing it but if he/she pauses in the middle of the word the search will start and will not stop until it finishes even if he/she starts typing again.

What I would like to do is stop searches while the user is typing, possibly in the inputFileName_TextChanged method.

Is there a way to stop a search that has been already started when using Directory.GetFiles?

using System.Timers;
using System.IO;

namespace findFileTest
{

    public partial class MainWindow : Window
    {
        Timer timer = new Timer();

        public MainWindow()
        {
            InitializeComponent();

            timer.Elapsed += new ElapsedEventHandler(TimerEvent);
            timer.Interval = 1000;
            timer.Enabled = true;
            timer.Stop();
        }

        private void inputFileName_TextChanged(object sender, TextChangedEventArgs e)
        {
            // How to stop the search currently in process here
            timer.Stop();
            timer.Start();
        }

        public void TimerEvent(object source, ElapsedEventArgs e)
        {
            timer.Stop();
            findFile();
        }

        private void findFile()
        {
            string fName = "";

            this.Dispatcher.Invoke(() => {
                fName = inputFileName.Text;
            });

            var fType = ".txt";
            var fileName = fName.Trim() + fType;

            var file = Directory.GetFiles(@"C:\TestFolder\", fileName, SearchOption.AllDirectories).FirstOrDefault();
            if (file == null) {
                Console.WriteLine("File not found");
            }
            else {
                Console.WriteLine("Found:" + file);
            }
        }
    }
}
Fulminant answered 22/10, 2017 at 15:26 Comment(1)
Wrong method, Directory.EnumerateFiles() is very easy to stop. Just break out of the foreach loop.Philipphilipa
L
6

Is there a way to stop a search that has been already started when using Directory.GetFiles?

No, not safely. The GetFiles() method doesn't provide any mechanism to interrupt its work before it returns. You would have to abort the thread, which would cause a host of other problems.

As noted in this comment, taking your request literally the answer would be to use the Directory.EnumerateFiles() method instead. This method surfaces the semantics of the underlying FindFirstFile() and FindNextFile(), by providing an enumerator that returns one file at a time. Every time a new file is returned, you have the opportunity to interrupt the enumeration and return without doing more work.

Note that even using EnumerateFiles(), you won't get the first file result until it finds something that matches your search pattern. It is likely that if you switch to using EnumerateFiles(), you'll also want to do your own matching on the input, rather than letting EnumerateFiles() do it. I.e. pass "*" as the search string. This will slow the overall operation down a little, but you'll get a chance to interrupt the operation for every file in the search directory and subdirectories, instead of only when a matching file is found.

Finally, I'll suggest that given your apparent use case, you may want to rethink using the Directory class at all. Based on the code you posted, you are initiating a search of the file system, a necessarily slow operation, every time the user has entered some new text. IMHO, it would be better to build an index once (e.g. a Dictionary<string, List<string>> that maps the file name without extension — i.e. Path.GetFileNameWithoutExtension() — to the full paths of each matching file in the directory). In that way, you don't have to interrupt anything. The "search" part after the user's entered text will be nearly instantaneous.

There will of course be an initial delay before the directory is fully indexed. It's up to you how you'd want to deal with that. But I'd recommend providing a mechanism both to indicate to the user that the indexing isn't fully complete, but also doing the check on what you have, just in case the file the user's looking for matches a file that was already indexed.

Note that the usual Windows file systems that are used — FAT, FAT32, NTFS, etc. — are case-insensitive, so you'd want to initialize your dictionary with StringComparer.OrdinalIgnoreCase. "Ordinal", because the file system names need to match the exact characters a user types, and "ignore case" so that the search is case-insensitive.

I'd guess that in your scenario, it's unlikely files would be added or removed while the user is using your program to search for them. But if you are concerned about that scenario, you can either just provide the user a "Refresh" button to force the directory to be re-indexed, or use FileSystemWatcher to update the index automatically if files are added or removed.

Leftwards answered 23/10, 2017 at 0:13 Comment(4)
@ Peter Duniho In your first two paragraphs, you answered a question I had about why use the Directory.EnumerateFiles() instead of Directory.GetFile(), thanks. The idea of building an indexing sounds very good but it sounds I little more complicated, but I will definitely look into it. Quick question, how are indexed files typically handled, you get the Dictionary list and then you save it so you can reference it later?Fulminant
Yes. The List<string> is a list of full paths of all the file names for a given name. E.g. if you had three directories, each with a file named "foo.txt", then your dictionary would have under the "foo" key a list with the full paths of those three files. When you build the index, you'll need to use TryGetValue() to see if the key is already present; if it is, you just add the new file you found to that key's list, and if it's not, you create the list first, add it as the value for that key, and add the new file to the new list.Leftwards
It's starting to make sense, the file name without the extension will be the key and the path will be the value in the List. Now, since once the first index is done most of the requests will be done through the List and not by searching, is there a limitation as to how big the List can be? Thanks.Fulminant
"most of the requests will be done through the List " -- if you are building and using an index, all searches should go through the index. "is there a limitation as to how big the List can be?" -- all computers are finite machines, so yes...there is a limit. But the List<T> class uses an array for its underlying storage, and .NET arrays can be 2GB in size. So, while there is a limit, that limit is far larger than you're likely to ever need. If you do somehow exceed that limit, you'll just need a more elaborate data structure (e.g. list of lists for the dictionary values).Leftwards

© 2022 - 2024 — McMap. All rights reserved.