【问题标题】:Threading and Recursion in C#C#中的线程和递归
【发布时间】:2016-02-10 18:43:19
【问题描述】:

我有这个函数,它接受一个路径并在其中搜索一个文件。我有两种方法:一种是让主线程完成工作,另一种是让工作线程完成工作。当主线程完成工作时,它返回所有文件,但当工作线程完成工作时,它只返回该路径中的少量文件。它不执行进入子目录的递归步骤。代码如下:

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);
                    }
                }
            } 

这是我调用线程的方式:

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

【问题讨论】:

  • 问题是什么?
  • @ScottHunter 我需要知道是什么原因造成的,但我认为这是递归
  • 我怀疑您收到了错误消息。这是因为您试图将项目添加到 ListView 对象。这在创建 ListView 的线程以外的任何线程上均无效。您是否检查过控制台是否写入了任何错误?
  • 我有多愚蠢@JeremyWest 你是对的,错误是因为列表视图,但你能告诉我如何将结果发送回主踏板吗?
  • 不要自己遍历文件系统; Directory 类具有获取目录中任何深度的所有文件的方法。就用那个吧。

标签: c# multithreading recursion


【解决方案1】:

您的代码在后台线程上运行时失败,因为您试图从 GUI 线程以外的线程修改控件,这是不允许的。要解决此问题,您必须将结果累积到后台线程上的某种集合中,然后从主线程填充列表视图。

但是,有一个内置函数可以完成您想要的。因此,最简单且可能最好的解决方案如下:

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

如果你想手动做,你仍然可以使用你的方法进行一些修改,例如:

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);
    }
  }
}

然后您可以像这样同步调用它:

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

在异步情况下,您需要等待后台线程填充列表,然后主线程才会填充列表视图。使用任务和 asyncawait 关键字非常简单。

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);
  }
}

【讨论】:

  • 很好的答案。您可能想谈谈为什么 OP 的线程方案失败了。
  • 哦,我在评论中做过,但对读者来说可能并不明显。谢谢提醒!
  • 大声笑,你知道我刚刚意识到你在上面写了评论。 (我正要说“在上面看到某某的评论”。我的错:)
  • 可能值得在响应中包含它。您不会是唯一一个看不到它并且对为什么这是解决方案感到有些困惑的人。
【解决方案2】:

现有代码的问题是您正在从非 UI 线程更新 UI 元素。您当然可以在后台线程上收集数据,但在添加到列表视图之前,您必须将数据编组回 UI。

要做到这一点,你应该让你的生活变得简单,并使用一个避免你需要滚动自己的线程的框架。

我建议使用 TPL、await/async 或 Microsoft 的响应式框架。我的选择是后者。

首先定义一个 observable 来查询所有文件:

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;
}

请注意,此方法是递归的,并使用后台线程(通过Scheduler.Default)来完成工作。

现在你需要消耗它

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

.ObserveOn(listView1) 将所有的编组返回到 UI 线程。 listView1 参数可以是任何 UI 窗体或控件。

.Subscribe(...) 只获取所有文件名并添加到列表视图中。

这段代码应该很容易使用。只需 NuGet Rx-WinFormsRx-WPF 即可开始使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-27
    • 2012-10-22
    • 2019-04-27
    相关资源
    最近更新 更多