C#在遍历文件结构时,如何判断所有线程或任务何时完成



此应用程序处理符合某些标准的图像文件,即分辨率大于1600X1600。源目录树中可能有8000多个文件,但并非所有文件都符合解析标准。这棵树至少可以有4层或5层深。

我已经"任务";实际的转换过程。然而,我不知道最后一项任务是什么时候完成的。

我真的不想创建任务数组,因为它会包含数千个不符合解析标准的文件。打开图像文件,检查分辨率,添加或不添加到任务数组,然后在处理文件时再次打开它,这似乎是一种浪费。

这是代码。

using System;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ResizeImages2
{
public partial class Form1 : Form
{
private string _destDir;
private string _sourceDir;
private StreamWriter _logfile;
private int _numThreads;
private void button1_Click(object sender, EventArgs e)
{
_numThreads = 0;
if (_logfile is null)
_logfile = new StreamWriter("c:\temp\imagesLog.txt", append: true);
_destDir = "c:\inetpub\wwwroot\mywebsite\";
_soureDir = "c:\images\"
Directory.CreateDirectory(_destDir);
var root = new DirectoryInfo(sourceDir);
WalkDirectoryTree(root); // <--async so it's going to return before all the threads are complete. Can't close the logfile here
//_logfile.Close();
}
private async void WalkDirectoryTree(System.IO.DirectoryInfo root)
{
System.IO.FileInfo[] files = null;
System.IO.DirectoryInfo[] subDirs = null;
files = root.GetFiles("*-*-*.jpg"); //looks like a sku and is a jpg.
if (files == null) return;
foreach (System.IO.FileInfo fi in files)
{
_numThreads++;
await Task.Run(() =>
{
CreateImage(fi);
_numThreads--;
return true;
});
}
// Now find all the subdirectories under this directory.
subDirs = root.GetDirectories();
foreach (System.IO.DirectoryInfo dirInfo in subDirs)
{
WalkDirectoryTree(dirInfo);
}
}
private void CreateImage(FileSystemInfo f)
{
var originalBitmap = new Bitmap(f.FullName);
if (originalBitmap.Width <= 1600 || originalBitmap.Height <= 1600) return;
using (var bm = new Bitmap(1600, 1600))
{
Point[] points =
{
new Point(0, 0),
new Point(new_wid, 0),
new Point(0, new_hgt),
};
using (var gr = Graphics.FromImage(bm))
{
gr.DrawImage(originalBitmap, points);
}
bm.SetResolution(96, 96);
bm.Save($"{_destDir}{f.Name}", ImageFormat.Jpeg);
bm.Dispose();
originalBitmap.Dispose();
}
_logfile.WriteLine(f.Name);
}
}
}

更新-最终结果

private async void button1_Click(Object sender, EventArgs e)
{
_logfile = new StreamWriter("c:\temp\imagesLog.txt", append: true);
_sourceDir=$"c:\sourceImages\";
_destDir = $"c:\inetpub\wwwroot\images\";
var jpgFiles = Directory.EnumerateFiles(_sourceDir, "*-*-*.jpg", SearchOption.AllDirectories);
var myPOpts = new ParallelOptions {MaxDegreeOfParallelism = 10};
await Task.Run(() => //allows the user interface to be updated -- usually, file being processed
{
Parallel.ForEach(jpgFiles,myPOpts, f=>
{
CreateImage(new FileInfo(f));
});
_logfile.Close();
});
}
private void CreateImage(FileSystemInfo f)
{
var originalBitmap = new Bitmap(f.FullName);
if (originalBitmap.Width <= 1600 || originalBitmap.Height <= 1600)
{
originalBitmap.Dispose();
return;
}
tbCurFile.Text = f.FullName;
var new_wid = 1600;
var new_hgt = 1600;
using (var bm = new Bitmap(new_wid, new_hgt))
{
Point[] points =
{
new Point(0, 0),
new Point(new_wid, 0),
new Point(0, new_hgt),
};
var scount = 1;
var saved = false;
//why the while/try/catch? Because we are copying files across the internet from a dropbbox in Sync Only Mode.
//this means we are only working with a pointer until we actually open the file and sometimes 
//the app gets ahead of itself. It tries to draw the new image before the original file is completely open
//so let's see if can't  mitigate that here.
while (!saved && scount < 5)
{
try
{
using (var gr = Graphics.FromImage(bm))
{
gr.DrawImage(originalBitmap, points);
}
saved = true;
}
catch
{
scount++;
}
}
bm.SetResolution(96, 96);
scount = 1;
saved = false;
while (!saved && scount<5)
{
try
{
bm.Save($"{_destDir}{f.Name}", ImageFormat.Jpeg);
saved = true;
}
catch
{
scount++;
}
}                
bm.Dispose();
originalBitmap.Dispose();
}
_logfile.WriteLine($"{_collectionId}\{f.Name}");
}

一种快速而肮脏的处理方式是使用具有共享变量的互锁操作。


private int _numberOfOutstandingOperations;

private async void button1_Click(object sender, EventArgs e)
{
Interlocked.Exchange(ref _numberOfOutstandingOperations, 1);

await WalkDirectoryTree(root);
CompleteOperation();
}


private async Task WalkDirectoryTree(System.IO.DirectoryInfo root)
{
// ...   
foreach (System.IO.FileInfo fi in files)
{
Interlocked.Increment(ref _numberOfOutstandingOperations);
_ = Task.Run(() =>
{
CreateImage(fi);
CompleteOperation();
});
}

foreach (System.IO.DirectoryInfo dirInfo in subDirs)
{
await WalkDirectoryTree(dirInfo);
}
}

private void CompleteOperation()
{
if (Interlocked.Decrement(ref _numberOfOutstandingOperations) == 0)
{
// Everything is done, signal a mutex or complete a TaskCompletionSource or whatever
}
}

诀窍是将操作数初始化为1,这样它只有在所有任务都完成所有目录都已遍历时才能完成。

最新更新