简体   繁体   中英

Threading and Recursion in C#

I have this function which takes a path and searches for a file in it. I have two approaches: one is to make the main thread do the job, and the another is to make a worker thread do the job. When the main thread does the job it returns all the files but when a worker thread does the job it returns only a small number of the files in that path only. It doesn't do the recursion step which enters sub-directories. Here is the code:

public void  GetAllFiles(string sdir)
        {

                foreach (string dir in Directory.GetDirectories(sdir))
                {

                    try
                    {
                        foreach (string file in Directory.GetFiles(dir, "*.*"))
                        {
                            string filename = Path.GetFileName(file);

                            listView1.Items.Add(filename);

                        }
                        GetAllFiles(dir);
                    }
                    catch (Exception error)
                    {
                        Console.WriteLine(error.Message);
                    }
                }
            } 

and here is how i call the thread:

 Thread thread = new Thread(() => GetAllFiles("C:\\Users\\modz\\Desktop\\games"));
 thread.Start();

Your code fails when run on a background thread because you are trying to modify a control from a thread other than the GUI thread, which is not allowed. To fix this, you must accumulate the results into some kind of collection on the background thread and then populate the list view from the main thread.

However, there is a built-in function that accomplishes what you want. So the simplest and likely best solution is the following:

var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
foreach (var file in files) {
  listView1.Items.Add(file);
}

If you want to do it manually, you can still use your method with some modification, such as:

public void GetAllFiles(string sdir, List<string> files) {
  foreach (string dir in Directory.GetDirectories(sdir)) {
    try {
      foreach (string file in Directory.GetFiles(dir, "*.*")) {
        string filename = Path.GetFileName(file);
        files.Add(filename);
      }
      GetAllFiles(dir, files);
    } catch (Exception error) {
      Console.WriteLine(error.Message);
    }
  }
}

You can then call this synchronously like this:

var files = new List<string>();
GetAllFiles(path, files);
foreach (var file in files) {
  listView1.Items.Add(file);
}

In the asynchronous case, you need to wait for the background thread to fill the list before the main thread populates the list view. Using tasks and the async and await keywords is pretty simple.

public async void Populate() {
  const string path = ...
  var files = new List<string>();
  await Task.Run(() => GetAllFiles(path, files));
  foreach (var file in files) {
    listView1.Items.Add(file);
  }
}

Your problem with the existing code is you are updating a UI element from a non-UI thread. You can certainly collect your data on a background thread, but you must marshall the data back to the UI before adding to your list view.

To do this you should make your life simple and use a framework that avoids you needing to roll your own threads.

I would suggest using TPL, await / async , or Microsoft's Reactive Framework. My choice is that latter.

First define an observable to query all of the files:

public IObservable<string> GetAllFiles(string sdir)
{
    return
        from dir in Directory.GetDirectories(sdir).ToObservable(Scheduler.Default)
        from file in Directory.GetFiles(dir, "*.*").ToObservable(Scheduler.Default)
            .Concat(GetAllFiles(dir))
        select file;
}

Note that this method is recursive and uses background threads (via Scheduler.Default ) to do the work.

Now you need to consume it

GetAllFiles("C:\\Users\\modz\\Desktop\\games")
    .ObserveOn(listView1)
    .Subscribe(filename => listView1.Items.Add(filename));

The .ObserveOn(listView1) does all of the marshalling back to the UI thread. The listView1 parameter can be any UI form or control.

The .Subscribe(...) just takes all of the file names and does the add on to the list view.

This code should be very easy to use. Just NuGet Rx-WinForms or Rx-WPF to get started.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM