求解决方法:遍历硬盘上所有文件

龙宜坡 2010-01-26 06:43:01
要求:

遍历指定路径下所有文件,生成文件名列表.(实际文件数量可能数亿个)

当前的做法:

使用BackGroundWorker后台遍历所有文件,其中使用到了递归。
当指定路径下存在文件个数较少,花费的处理时间较短时,没有问题。

文件较多花费的处理时间较长时,会出现以下错误:

上下文“0x1a1978”已断开连接。正在从当前上下文(上下文 0x1a1808)释放接口。这可能会导致损坏或数据丢失。要避免此问题,请确保在应用程序全部完成 RuntimeCallableWrapper (表示其内部的 COM 组件)之前,所有上下文/单元都保持活动状态。

google&BaiDu结果:disconnectedContext MDA


——————————————————————————————————————————————

本想使用:Directory.GetFiles(String, String, SearchOption) 方法
string[] files = System.IO.Directory.GetFiles("F:\\", "*.xls", SearchOption.AllDirectories);

但是此种方法出现新的问题:当路径包含无访问权限的目录时不会返回任何东西。


没解决问题,来这里求大家给个思路。

谢谢@!

代码明天整理后贴上来大家再给看看。
...全文
1564 53 打赏 收藏 转发到动态 举报
写回复
用AI写文章
53 条回复
切换为时间正序
请发表友善的回复…
发表回复
龙宜坡 2010-07-19
  • 打赏
  • 举报
回复
:)
海涵德 2010-07-18
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 nosort 的回复:]
是因为C#的堆栈溢出,前面递归的FileSysinfo被强制卸载,唉。。。

你用VB.net然后改用
IO.Directory.GetDirectories()
IO.Directory.GetFiles()
来试试,一定通过。
[/Quote]
你的代码与楼主的代码是一样的。
海涵德 2010-07-18
  • 打赏
  • 举报
回复
[Quote=引用楼主 goga21cn 的回复:]
要求:

遍历指定路径下所有文件,生成文件名列表.(实际文件数量可能数亿个)

当前的做法:

使用BackGroundWorker后台遍历所有文件,其中使用到了递归。
当指定路径下存在文件个数较少,花费的处理时间较短时,没有问题。

当文件较多花费的处理时间较长时,会出现以下错误:

上下文“0x1a1978”已断开连接。正在从当前上下文(上下文 0x1a1808)释放接口……
[/Quote]
我遇到同你一样的问题,贴在vb.net上了,从那里有人给连接到你这里,我还以为是我发的原贴呢,所以就会了上面的帖子,不管怎么说,算是解决了,就是时间太长。
海涵德 2010-07-18
  • 打赏
  • 举报
回复
多谢诸位的热心帮助,在我刚看到你们的贴子时我的后台线程终于返回结果,时间大概是:18:20,第一个帖子的时间是16:53,用时1个小时多。现将调试贴图贴上供大家参考:

贴图上有些地方剪掉了,请诸位谅解。不管怎么说问题解决了,时间太长了,处理这么多文件等待也值了。
部分代码如下:
#Region " - 线程处理"
Private _t As Thread
Private Event event_thread_end(ByVal file_info_list() As FileInfo)

Private Sub background_process(ByVal directory_info As DirectoryInfo)
Dim di As DirectoryInfo
Dim directory_name As String
Dim fi As FileInfo
Dim fi_list() As FileInfo
Dim full_file_name As String

full_file_name = "E:\madaming\Download\m00000001.jpg" '
fi = New FileInfo(full_file_name)
directory_name = fi.DirectoryName

di = New DirectoryInfo(directory_name)
Try

fi_list = di.GetFiles()
System.Threading.Thread.Sleep(1000)

RaiseEvent event_thread_end(fi_list)

Catch ex As Exception
RaiseEvent event_thread_exception(ex.Message)
End Try

End Sub

Private Sub Button_start_thread_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_start_thread.Click

_t = New Thread(AddressOf background_process)
_t.Start()

End Sub
''''''''
...

Private Sub Button_StopThread_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_StopThread.Click
_t.Abort()
End Sub

#End Region
holyassassin 2010-04-19
  • 打赏
  • 举报
回复
学习来的
龙宜坡 2010-01-27
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 lost_painting 的回复:]
粗略看了下代码,
根据楼主说的有几亿个文件.
应该是内存溢出了...
楼主可以去循环10亿次 (_fliename + System.Environment.NewLine);
假设 一个FileName字长10个汉字
(10*2个字节+换行符) X 10亿 = 大约 19G
(粗略算的)
因此,肯定溢出

rtxtResult.AppendText(_fliename + System.Environment.NewLine);
rtxtResult.ScrollToCaret();


引用 10 楼 goga21cn 的回复:
引用 4 楼 wuyq11 的回复:
WIN32API来遍历文件和目录
FindFirstFile,FindNextFile和FindClose
参考


我搜到这个结果,不过还是想知道上下文“0x1a1978”已断开连接。 这段出错的原因及解决方法。


谢了!

[/Quote]

先不考虑这个,最终还是要处理成边读取边缓存到其它地方,目前暂时不考虑,我目前测试的目录下也就数十万个文件而已。


[Quote=引用 16 楼 nosort 的回复:]
是因为C#的堆栈溢出,前面递归的FileSysinfo被强制卸载,唉。。。

你用VB.net然后改用
IO.Directory.GetDirectories()
IO.Directory.GetFiles()
来试试,一定通过。

[/Quote]

谢谢,明天一定试试 看。

[Quote=引用 19 楼 chrisak 的回复:]
上亿个...堆栈溢出或者同一目录下的文件过多内存不足了吧.

如果是堆栈溢出,用栈改写递归算法;可以把部分栈内容缓冲到硬盘文件.
如果是内存不足...改用Winapi的Findxxxx系列函数.
[/Quote]

应该是堆栈溢出了。
"用栈改写递归算法",可以举个例子吗?


[Quote=引用 21 楼 ouc_ajax 的回复:]
楼主,我拷贝你的代码到我机器上,第一次出现同样异常。
改后异常没有。

如果你的文件确实多,试过我的代码还是有异常,就把rtxtResult.ScrollToCaret();
这句话去掉吧...考虑一开始提示开始搜索,然后线程回调的方式去掉提示吧....
[/Quote]

首先谢谢这位兄弟。

的确如你所说,有可能是因为加了多线程线程之间切换的原因导致的上下文丢失!

至于细节有待于进一步验证。

[Quote=引用 22 楼 ouc_ajax 的回复:]
我换了台机器试了下,文件多了(过10万),有rtxtResult.ScrollToCaret(); 还是会溢出,
如果去掉这句不会抛异常,不过没有用户界面提示(假死)

我感觉如果文件很多的话,还是不要使用这种方式, 一开始提示加载(可以做个滚动条),然后调用搜索函数,最后函数回调,去掉加载提示。
[/Quote]

一开始我本不打算用多线程,甚至想用命令行程序实现,但是与用户多次沟通后还是回到了WinForm上来,且必须考虑用户友好性。


[Quote=引用 24 楼 ouc_ajax 的回复:]
引用 15 楼 lost_painting 的回复:
粗略看了下代码,
根据楼主说的有几亿个文件.
应该是内存溢出了...
楼主可以去循环10亿次 (_fliename + System.Environment.NewLine);
假设 一个FileName字长10个汉字
(10*2个字节+换行符) X 10亿 = 大约 19G
(粗略算的)
因此,肯定溢出


1亿个文件 : (10*2 byte)* 1亿 = 20亿 byte = (20亿/1024*1024*1024)*8 = 15G
楼主,好像的确会溢出....(还是限制下搜索条件吧...或者用TreeView来加载文件夹内文件(动态加载))
[/Quote]

同15楼回复。

[Quote=引用 27 楼 jiyuanlong 的回复:]
每次遍历1000个。与数据库分页原理一样。
[/Quote]

同15楼回复。


再次感谢各位,及其"帮顶"的各位同仁。


这个代码完成后我会放上来与大家分享。
龙宜坡 2010-01-27
  • 打赏
  • 举报
回复
在后台线程中仅仅加上了Thread.Sleep(10),就不会出现上下文丢失,主要原因可能是频繁的进行线程切换引起的,不过加上后运行半小时还会CPU占用率很高(>85%).

真是牺牲性能的损失换取用户界面响应。

先看看代码,请帮忙优化下,谢谢
public partial class frmMain : Form
{
/// <summary>
/// 路径
/// </summary>
private string m_path;
/// <summary>
/// 扩展名
/// </summary>
private string m_ext;
/// <summary>
/// 文件总数量
/// </summary>
private int m_filesCount;
/// <summary>
/// 后台线程
/// </summary>
private BackgroundWorker m_bgworker = null;
/// <summary>
/// 目录信息
/// </summary>
private DirectoryInfo m_dirInfo = null;

private List<string> m_FileNames = new List<string>();

public frmMain()
{
InitializeComponent();
}

private void btnOpenPath_Click(object sender, EventArgs e)
{
FolderBrowserDialog _fbd = new FolderBrowserDialog();
_fbd.ShowNewFolderButton = false;
if (_fbd.ShowDialog(this) == DialogResult.OK)
{
txtPath.Text = _fbd.SelectedPath;
}
}

private void btnProcess_Click(object sender, EventArgs e)
{
m_FileNames.Clear();

rtxtResult.Text = "";
m_path = txtPath.Text;
m_ext = txtExt.Text;
m_filesCount = 0;
m_bgworker = new BackgroundWorker();
m_bgworker.WorkerReportsProgress = true;
m_bgworker.WorkerSupportsCancellation = true;
m_bgworker.DoWork += new DoWorkEventHandler(DoWork);
m_bgworker.ProgressChanged += new ProgressChangedEventHandler(ReportProgress);
m_bgworker.RunWorkerCompleted+=new RunWorkerCompletedEventHandler(RunWorkerCompleted);
m_bgworker.RunWorkerAsync();
}

private void DoWork(object sender, DoWorkEventArgs dwea)
{
BackgroundWorker _worker = sender as BackgroundWorker;
ErgodicFile(_worker, m_path, m_ext);
}

/// <summary>
/// 遍历指定路径下所有指定扩展名的文件
/// </summary>
/// <param name="path">指定路径</param>
/// <param name="ext">文件扩展名,不包含分隔符(.)</param>
/// <returns>返回所有文件绝对路径</returns>
private void ErgodicFile(BackgroundWorker worker, string path, string extension)
{
if (worker.CancellationPending)
{
return;
}

Thread.Sleep(10);
FileSystemInfo[] _fsi = null;
try
{
m_dirInfo = new DirectoryInfo(path);
_fsi = m_dirInfo.GetFileSystemInfos();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}

FileInfo _fi;
try
{
for (int i = 0; i < _fsi.Length && !worker.CancellationPending; i++)
{
if (!Directory.Exists(_fsi[i].FullName))
{//是文件
_fi = new FileInfo(_fsi[i].FullName);
//判断文件扩展名
if (m_ext.Length > 0)
{//要求扩展名
if (_fi.Extension == m_ext)
{
m_filesCount++;
worker.ReportProgress(0, _fi.FullName);
m_FileNames.Add(_fi.FullName);
Thread.Sleep(10);
}
}
else
{
worker.ReportProgress(0, _fi.FullName);
m_FileNames.Add(_fi.FullName);
Thread.Sleep(10);
}
}
else
{//是目录,继续遍历
ErgodicFile(worker, _fsi[i].FullName, extension);
Thread.Sleep(10);
}
Thread.Sleep(10);
}
Thread.Sleep(10);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(DialogResult.OK==MessageBox.Show("扫描完成,共找到"+m_FileNames.Count+"个文件,点击确定查看","文件扫描完成",MessageBoxButtons.OKCancel))
{}
}

private void ReportProgress(object sender, ProgressChangedEventArgs e)
{
string _fileName = e.UserState.ToString();
rtxtResult.AppendText(_fileName + System.Environment.NewLine);
rtxtResult.ScrollToCaret();
lblCount.Text = rtxtResult.Lines.Length.ToString();
}

private void btnStop_Click(object sender, EventArgs e)
{
if (m_bgworker != null && m_bgworker.WorkerSupportsCancellation)
{
m_bgworker.CancelAsync();
}
}

private void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
if (m_FileNames != null && m_FileNames.Count > 0)
{
frmFileRead _frm = new frmFileRead(m_FileNames);
_frm.ShowDialog(this);
}
btnStart.Enabled = true;
}

}


看来还是没有使用好多线程。

大牛说的关于多线程最经典的论述:

多线程的优劣/性能/系统开销

线程创建之前
1.系统为线程分配并初始化一个线程内核对象;
2.系统为每个线程保留1MB的地址空间(按需提交)用于线程用户模式堆栈;
3.系统为线程分配12KB(左右)的地址空间用于线程的内核模式堆栈。
线程创建之后
4.Windows调用当前进程中的每个DLL都有的一个函数,用来通知进程中的所有DLL,操作系统创建了一个新的线程。
销毁一个线程时
5.当前进程中的所有DLL都要接收一个关于该线程即将"死亡"的通知;
6.线程的内核对象及创建时系统分配的堆栈需要释放。


如果某台计算机只有一个CPU的话,则在某一时刻只有一个线程可以运行。
Windows必须跟踪记录线程对象,而且不停地跟踪记录每个线程对象。
Windows必须决定CPU下一个次(每隔约20毫秒)调度那一个线程使其运行。
上下文切换(Context switch):Windows使CPU停止执行一个线程的代码,而开始执行另一个线程的代码的现象,我们称之为上下文切换。

上下文切换的开销:

1.进入内核模式;
2.将CPU的寄存器保存到当前正在执行的线程的内核对象中。
注明:X86架构下CPU寄存器占了大约700字节(Byte)的空间,X64架构下CPU寄存器大约占了1024(Byte)的空间,IA64架构下 CPU寄存器占了大约2500Byte的空间。
3.需要一个自旋锁(spin lock),确定下一次调度那一个线程,然后再释放该自旋锁。
如果下一次调度的线程属于同一个进程,哪么此处开销更大,因为OS必须先切换虚拟地址空间。
4.把即将要运行的线程的内核对象的地址加载到CPU寄存器中。
5.退出内核模式。


以上都是纯粹的开销,导致Windows和应用程序的执行速度比在单线程系统上的执行速度慢。
综上所述:应尽量限制线程的使用。


多线程的带来的好处:
1.健壮性。
此线程的错误不会影响彼线程。
2.可扩展性。
多个CPU情况下,可充分发挥多个CPU的优势。

wang527514926 2010-01-27
  • 打赏
  • 举报
回复
学习了!
khjian 2010-01-27
  • 打赏
  • 举报
回复
优化算法,分步遍历
silentwins 2010-01-27
  • 打赏
  • 举报
回复
mark
yuanhuiqiao 2010-01-27
  • 打赏
  • 举报
回复
分步遍历是个思想
cjnkd 2010-01-27
  • 打赏
  • 举报
回复
帮顶
MisuzuAyu 2010-01-27
  • 打赏
  • 举报
回复
好贴~~~学习了~~~
xiyeye 2010-01-27
  • 打赏
  • 举报
回复
关注一下,很有用,马上也要用到这个
feixue_XXXX 2010-01-27
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 lost_painting 的回复:]
运行cmd -->
cd\
f:
F:\>dir /s /a *.* >1.txt

呵呵,历遍搞定


[/Quote]

这个强,速度快,也能解决你的问题。
qqiuzaihui 2010-01-27
  • 打赏
  • 举报
回复
帮顶并接分升星回家过年相亲娶老婆.

tommir3 2010-01-27
  • 打赏
  • 举报
回复
帮顶加学习
wufanglu 2010-01-27
  • 打赏
  • 举报
回复
DOS能做到的,Windows(指的是其面向对象编程)不能做到?性能方面
swan01 2010-01-27
  • 打赏
  • 举报
回复
可以边遍历目录树边把它转换成队列,节省了递归的空间,延长了cpu的处理时间。
aimeast 2010-01-27
  • 打赏
  • 举报
回复
要不lz就用api实现,速度非常快。
加载更多回复(33)

110,533

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧