C#Threading - 同时读取和散列多个文件,最简单的方法?

A. *_*mas 12 c# hash multithreading

我一直试图得到我认为最简单的线程形式在我的应用程序中工作,但我不能这样做.

我想做什么:我有一个主窗体,上面有状态条和进度条.我必须阅读3到99个文件之间的内容,并将它们的哈希值添加到字符串[]中,我想将其添加到所有文件的列表及其各自的哈希值.之后我必须将该列表中的项目与数据库(文本文件中的数据库)进行比较.完成所有操作后,我必须将主窗体和进度条中的文本框更新为33%; 大多数情况下,我只是不希望主窗体在处理过程中冻结.

我正在使用的文件总是达到1.2GB(+/-几MB),这意味着我应该能够将它们读入byte []并从那里处理它们(我必须计算CRC32,MD5和SHA1)这些文件中的每一个应该比从HDD中读取所有文件的速度快3倍.

另外我应该注意一些文件可能是1MB而另一个可能是1GB.我最初想为99个文件创建99个线程,但这似乎不明智,我想最好重用小文件的线程,而更大的文件线程仍在运行.但这对我来说听起来很复杂,所以我不确定这是否也是明智之举.

到目前为止,我已经尝试过工作者和背景工作者,但似乎对我来说似乎都不太好; 至少backgroundWorkers工作了一些时间,但我甚至无法弄清楚为什么他们不会在其他时间......主要形式仍然冻结的方式.现在我已经阅读了.NET 4.0中的任务并行库,但我认为在浪费更多时间之前我应该​​更好地问一个知道自己在做什么的人.

我想做的事情看起来像这样(没有线程):

List<string[]> fileSpecifics = new List<string[]>();

int fileMaxNumber = 42; // something between 3 and 99, depending on file set

for (int i = 1; i <= fileMaxNumber; i++)
{
    string fileName = "C:\\path\\to\\file" + i.ToString("D2") + ".ext"; // file01.ext - file99.ext
    string fileSize = new FileInfo(fileName).Length.ToString();
    byte[] file = File.ReadAllBytes(fileName);
    // hash calculations (using SHA1CryptoServiceProvider() etc., no problems with that so I'll spare you that, return strings)
    file = null; // I didn't yet check if this made any actual difference but I figured it couldn't hurt
    fileSpecifics.Add(new string[] { fileName, fileSize, fileCRC, fileMD5, fileSHA1 });
}

// look for files in text database mentioned above, i.e. first check for "file bundles" with the same amount of files I have here; then compare file sizes, then hashes
// again, no problems with that so I'll spare you that; the database text files are pretty small so parsing them doesn't need to be done in an extra thread.
Run Code Online (Sandbox Code Playgroud)

是否有人能够指出我正确的方向?我正在寻找最简单的方法来快速读取和散列这些文件(我相信散列需要花费一些时间才能读取其他文件)并将输出保存到字符串[],而不会使主窗体冻结,仅此而已,没什么.

我很感激任何意见.

编辑澄清:由"工作在某些时候backgroundWorkers"我的意思是(对于非常同一组文件),也许我的代码的第一和第四的执行产生正确的输出和UI 5秒内解冻,第二,第三次和第五次执行它冻结表单(并在60秒后我收到一条错误消息,说某个线程在该时间范围内没有响应)并且我必须通过VS停止执行.

感谢你的所有建议和指示,因为你们都已经正确地猜到我对线程是全新的,并且必须阅读你们发布的优秀链接.然后我会试试这些方法并标出最能帮助我的答案.再次感谢!

sll*_*sll 18

使用.NET Framework 4.X

  1. 使用Directory.EnumerateFiles方法进行高效/惰性文件枚举
  2. 使用Parallel.For()将并行工作委托给PLINQ框架或使用TPL委派每个管道阶段的单个任务
  3. 使用管道模式管理以下阶段:计算哈希码,与模式进行比较,更新UI
  4. 为了避免UI冻结使用适当的技术:对于WPF使用Dispatcher.BeginInvoke(),对于WinForms使用Invoke(),请参阅此SO答案
  5. 考虑到所有这些东西都有UI添加一些消除功能放弃,如果需要长时间运行的操作可能是有用的,看看在CreateLinkedTokenSource它允许触发类CancellationToken从"外部范围"我可以尝试添加一个例子,但它是值得做你自己这样学习所有这些东西,而不是简单地复制/粘贴 - >让它工作 - >忘了它.

PS:必须阅读 - MSDN上的Pipelines论文


TPL特定的管道实现

  • 管道模式实现:三个阶段:计算哈希,匹配,更新UI
  • 三个任务,每个阶段一个
  • 两个阻塞队列

//

// 1) CalculateHashesImpl() should store all calculated hashes here
// 2) CompareMatchesImpl() should read input hashes from this queue
// Tuple.Item1 - hash, Typle.Item2 - file path
var calculatedHashes = new BlockingCollection<Tuple<string, string>>();


// 1) CompareMatchesImpl() should store all pattern matching results here
// 2) SyncUiImpl() method should read from this collection and update 
//    UI with available results
var comparedMatches = new BlockingCollection<string>();

var factory = new TaskFactory(TaskCreationOptions.LongRunning,
                              TaskContinuationOptions.None);


var calculateHashesWorker = factory.StartNew(() => CalculateHashesImpl(...));
var comparedMatchesWorker = factory.StartNew(() => CompareMatchesImpl(...));
var syncUiWorker= factory.StartNew(() => SyncUiImpl(...));

Task.WaitAll(calculateHashesWorker, comparedMatchesWorker, syncUiWorker);
Run Code Online (Sandbox Code Playgroud)

CalculateHashesImpl():

private void CalculateHashesImpl(string directoryPath)
{
   foreach (var file in Directory.EnumerateFiles(directoryPath))
   {
       var hash = CalculateHashTODO(file);
       calculatedHashes.Add(new Tuple<string, string>(hash, file.Path));
   }
}
Run Code Online (Sandbox Code Playgroud)

CompareMatchesImpl():

private void CompareMatchesImpl()
{
   foreach (var hashEntry in calculatedHashes.GetConsumingEnumerable())
   {
      // TODO: obviously return type is up to you
      string matchResult = GetMathResultTODO(hashEntry.Item1, hashEntry.Item2);
      comparedMatches.Add(matchResult);
   }
}
Run Code Online (Sandbox Code Playgroud)

SyncUiImpl():

private void UpdateUiImpl()
{
    foreach (var matchResult in comparedMatches.GetConsumingEnumerable())
    {
        // TODO: track progress in UI using UI framework specific features
        // to do not freeze it
    }
}
Run Code Online (Sandbox Code Playgroud)

TODO:考虑将CancellationToken所有GetConsumingEnumerable()调用用作参数,以便在需要时轻松停止管道执行.


Eri*_*ert 17

首先,您应该使用更高级别的抽象来解决此问题.您需要完成一系列任务,因此请使用"任务"抽象.您应该使用任务并行库来执行此类操作.让TPL处理要创建多少工作线程的问题 - 如果工作是在I/O上进行门控,那么答案可能会低至1.

如果你想做自己的线程,一些好的建议:

  • 不要阻止UI线程.这就是冻结你的申请的原因.提出一个协议,通过该协议,工作线程可以与您的UI线程进行通信,然后除了响应UI事件之外什么都不做.请记住,除了UI线程之外,任何其他线程都不能调用任务完成栏等用户界面控件的方法.

  • 不要创建99个线程来读取99个文件.这就像获得99件邮件并雇佣99名助手来回复:对于一个简单的问题,这是一个非常昂贵的解决方案.如果你的工作是CPU密集型的,那么"雇佣"线程没有任何意义,而不是你有CPU来维护它们.(这就像在一个只有四张办公桌的办公室雇用99名助理.助理大部分时间都在等待桌子而不是阅读你的邮件.)如果你的工作是磁盘密集型的,那么这些大部分线程都会进行大部分时间等待磁盘闲置,这是一个更大的资源浪费.