@@ -25,82 +25,103 @@ public FileSizeCalculator(params string[] paths)
2525
2626 public async Task ComputeSizeAsync ( CancellationToken cancellationToken = default )
2727 {
28- await Parallel . ForEachAsync (
29- _paths ,
30- cancellationToken ,
31- async ( path , token ) => await Task . Factory . StartNew ( ( ) =>
32- {
33- ComputeSizeRecursively ( path , token ) ;
34- } ,
35- token ,
36- TaskCreationOptions . LongRunning ,
37- TaskScheduler . Default ) ) ;
28+ const int ChunkSize = 1000 ;
29+ var queue = new Queue < string > ( _paths ) ;
30+ var batch = new List < string > ( ChunkSize ) ;
3831
39- unsafe void ComputeSizeRecursively ( string path , CancellationToken token )
32+ while ( ! cancellationToken . IsCancellationRequested && queue . TryDequeue ( out var currentPath ) )
4033 {
41- var queue = new Queue < string > ( ) ;
42- if ( ! Win32Helper . HasFileAttribute ( path , FileAttributes . Directory ) )
34+ if ( ! Win32Helper . HasFileAttribute ( currentPath , FileAttributes . Directory ) )
4335 {
44- ComputeFileSize ( path ) ;
36+ batch . Add ( currentPath ) ;
4537 }
4638 else
4739 {
48- queue . Enqueue ( path ) ;
49-
50- while ( queue . TryDequeue ( out var directory ) )
40+ try
5141 {
52- WIN32_FIND_DATAW findData = default ;
53-
54- fixed ( char * pszFilePath = directory + "\\ *.*" )
42+ foreach ( var file in Directory . EnumerateFiles ( currentPath ) )
5543 {
56- var hFile = PInvoke . FindFirstFileEx (
57- pszFilePath ,
58- FINDEX_INFO_LEVELS . FindExInfoBasic ,
59- & findData ,
60- FINDEX_SEARCH_OPS . FindExSearchNameMatch ,
61- null ,
62- FIND_FIRST_EX_FLAGS . FIND_FIRST_EX_LARGE_FETCH ) ;
63-
64- if ( ! hFile . IsNull )
44+ if ( cancellationToken . IsCancellationRequested )
45+ break ;
46+ batch . Add ( file ) ;
47+ if ( batch . Count >= ChunkSize )
6548 {
66- do
67- {
68- FILE_FLAGS_AND_ATTRIBUTES attributes = ( FILE_FLAGS_AND_ATTRIBUTES ) findData . dwFileAttributes ;
69-
70- if ( attributes . HasFlag ( FILE_FLAGS_AND_ATTRIBUTES . FILE_ATTRIBUTE_REPARSE_POINT ) )
71- // Skip symbolic links and junctions
72- continue ;
73-
74- var itemPath = Path . Combine ( directory , findData . cFileName . ToString ( ) ) ;
75-
76- // Skip current and parent directory entries
77- var fileName = findData . cFileName . ToString ( ) ;
78- if ( fileName . Equals ( "." , StringComparison . OrdinalIgnoreCase ) ||
79- fileName . Equals ( ".." , StringComparison . OrdinalIgnoreCase ) )
80- {
81- continue ;
82- }
83-
84- if ( attributes . HasFlag ( FILE_FLAGS_AND_ATTRIBUTES . FILE_ATTRIBUTE_DIRECTORY ) )
85- {
86- queue . Enqueue ( itemPath ) ;
87- }
88- else
89- {
90- ComputeFileSize ( itemPath ) ;
91- }
92-
93- if ( token . IsCancellationRequested )
94- break ;
95- }
96- while ( PInvoke . FindNextFile ( hFile , & findData ) ) ;
97-
98- PInvoke . FindClose ( hFile ) ;
49+ ComputeFileSizeBatch ( batch ) ;
50+ batch . Clear ( ) ;
51+ await Task . Yield ( ) ;
9952 }
10053 }
10154 }
55+ catch ( UnauthorizedAccessException ) { }
56+ catch ( IOException ) { }
57+ #if DEBUG
58+ catch ( Exception ex )
59+ {
60+ System . Diagnostics . Debug . WriteLine ( ex ) ;
61+ }
62+ #endif
63+
64+ try
65+ {
66+ foreach ( var dir in Directory . EnumerateDirectories ( currentPath ) )
67+ {
68+ if ( cancellationToken . IsCancellationRequested )
69+ break ;
70+ queue . Enqueue ( dir ) ;
71+ }
72+ }
73+ catch ( UnauthorizedAccessException ) { }
74+ catch ( IOException ) { }
75+ #if DEBUG
76+ catch ( Exception ex )
77+ {
78+ System . Diagnostics . Debug . WriteLine ( ex ) ;
79+ }
80+ #endif
81+
82+ }
83+
84+ if ( batch . Count >= ChunkSize )
85+ {
86+ ComputeFileSizeBatch ( batch ) ;
87+ batch . Clear ( ) ;
88+ await Task . Yield ( ) ;
10289 }
10390 }
91+
92+ if ( batch . Count > 0 )
93+ {
94+ ComputeFileSizeBatch ( batch ) ;
95+ batch . Clear ( ) ;
96+ }
97+ }
98+
99+ private void ComputeFileSizeBatch ( IEnumerable < string > files )
100+ {
101+ long batchTotal = 0 ;
102+ foreach ( var path in files )
103+ {
104+ if ( _computedFiles . ContainsKey ( path ) )
105+ continue ;
106+
107+ using var hFile = PInvoke . CreateFile (
108+ path ,
109+ ( uint ) FILE_ACCESS_RIGHTS . FILE_READ_ATTRIBUTES ,
110+ FILE_SHARE_MODE . FILE_SHARE_READ ,
111+ null ,
112+ FILE_CREATION_DISPOSITION . OPEN_EXISTING ,
113+ 0 ,
114+ null ) ;
115+
116+ if ( ! hFile . IsInvalid && PInvoke . GetFileSizeEx ( hFile , out long size ) )
117+ {
118+ if ( _computedFiles . TryAdd ( path , size ) )
119+ batchTotal += size ;
120+ }
121+ }
122+
123+ if ( batchTotal > 0 )
124+ Interlocked . Add ( ref _size , batchTotal ) ;
104125 }
105126
106127 private long ComputeFileSize ( string path )
0 commit comments