2017-05-23 23 views
-1

Zipファイルの内容を読み込み、解凍しようとしています。Zipファイルを解凍するときにParallel.ForEachが例外をスローする

var allZipEntries = ZipFile.Open(zipFileFullPath, ZipArchiveMode.Read).Entries; 

ここで、使用しているForeachループを抽出すると、これは問題なく動作します。欠点は、zip.extractメソッドと同等であり、すべてのファイルを展開しようとすると利点がありません。今

foreach (var currentEntry in allZipEntries) 
     { 
      if (currentEntry.FullName.Equals(currentEntry.Name)) 
      { 
       currentEntry.ExtractToFile($"{tempPath}\\{currentEntry.Name}"); 
      } 
      else 
      { 
       var subDirectoryPath = Path.Combine(tempPath, Path.GetDirectoryName(currentEntry.FullName)); 
       Directory.CreateDirectory(subDirectoryPath); 
       currentEntry.ExtractToFile($"{subDirectoryPath}\\{currentEntry.Name}"); 
      } 

     } 

はParallel.forEachを使用してみましたTPLを活用するために、それは次の例外を投げます:型 'System.IO.InvalidDataException' の例外がSystem.IO.Compressionで発生した

。 dllがユーザーコードで処理されませんでした

追加情報:ローカルファイルヘッダーが破損しています。

Parallel.ForEach(allZipEntries, currentEntry => 
     { 
      if (currentEntry.FullName.Equals(currentEntry.Name)) 
      { 
       currentEntry.ExtractToFile($"{tempPath}\\{currentEntry.Name}"); 
      } 
      else 
      { 
       var subDirectoryPath = Path.Combine(tempPath, Path.GetDirectoryName(currentEntry.FullName)); 
       Directory.CreateDirectory(subDirectoryPath); 
       currentEntry.ExtractToFile($"{subDirectoryPath}\\{currentEntry.Name}"); 
      } 

     }); 

そして、私はロックを使用することができ、これを回避するために、それは全体の目的を敗北。

 Parallel.ForEach(allZipEntries, currentEntry => 
     { 
      lock (thisLock) 
      { 
       if (currentEntry.FullName.Equals(currentEntry.Name)) 
       { 
        currentEntry.ExtractToFile($"{tempPath}\\{currentEntry.Name}"); 
       } 
       else 
       { 
        var subDirectoryPath = Path.Combine(tempPath, Path.GetDirectoryName(currentEntry.FullName)); 
        Directory.CreateDirectory(subDirectoryPath); 
        currentEntry.ExtractToFile($"{subDirectoryPath}\\{currentEntry.Name}"); 
       } 
      } 

     }); 

ファイルを抽出するのに他の方法がありますか?

+0

問題は、1つのzipファイルを読み込んでおり、そのファイルを並列に抽出しようとしていることです。あなたができることは、それをメモリに読み込んでから並列に抽出することです。しかし、ウィンドウがディスクに当たる行の最後では、書き込みIOはまだ並行しないでしょう。 – rolls

答えて

1

ハードドライブコントローラが1つずつ要求を実行するだけなので、並行して書き込み/読み取りすることはお勧めできません。複数のスレッドを持つことによって、オーバーヘッドを追加して、すべての利益を得ることができます。

ファイルを最初にメモリに読み込んでみてください。これはあなたの例外を回避しますが、ベンチマークすれば、実際にはより多くのスレッドのオーバーヘッドにより遅くなることがあります。

ファイルが非常に大きく、解凍に時間がかかる場合は、並列で解凍を実行すると速度が向上する可能性がありますが、IOの読み書きはできません。とにかく、ほとんどの減圧ライブラリはマルチスレッド化されているので、これを実行してもパフォーマンスが向上しません。

編集:以下のようにライブラリのスレッドを安全にするための厄介な方法です。これは、これは、並列

Array.ForEach(Directory.GetFiles(@"c:\temp\output\"), File.Delete); 

Stopwatch timer = new Stopwatch(); 
timer.Start(); 
int numberOfThreads = 8; 
var clonedZipEntries = new List<ReadOnlyCollection<ZipArchiveEntry>>(); 

for (int i = 0; i < numberOfThreads; i++) 
{ 
    clonedZipEntries.Add(ZipFile.Open(@"c:\temp\temp.zip", ZipArchiveMode.Read).Entries); 
} 
int totalZipEntries = clonedZipEntries[0].Count; 
int numberOfEntriesPerThread = totalZipEntries/numberOfThreads; 

Func<object,int> action = (object thread) => 
{ 
    int threadNumber = (int)thread; 
    int startIndex = numberOfEntriesPerThread * threadNumber; 
    int endIndex = startIndex + numberOfEntriesPerThread; 
    if (endIndex > totalZipEntries) endIndex = totalZipEntries; 

    for (int i = startIndex; i < endIndex; i++) 
    { 
     Console.WriteLine($"Extracting {clonedZipEntries[threadNumber][i].Name} via thread {threadNumber}"); 
     clonedZipEntries[threadNumber][i].ExtractToFile([email protected]"C:\temp\output\{clonedZipEntries[threadNumber][i].Name}"); 
    } 

    //Check for any remainders due to non evenly divisible size 
    if (threadNumber == numberOfThreads - 1 && endIndex < totalZipEntries) 
    { 
     for (int i = endIndex; i < totalZipEntries; i++) 
     { 
      Console.WriteLine($"Extracting {clonedZipEntries[threadNumber][i].Name} via thread {threadNumber}"); 
      clonedZipEntries[threadNumber][i].ExtractToFile([email protected]"C:\temp\output\{clonedZipEntries[threadNumber][i].Name}"); 
     } 
    } 
    return 0; 
}; 


//Construct the tasks 
var tasks = new List<Task<int>>(); 
for (int threadNumber = 0; threadNumber < numberOfThreads; threadNumber++) tasks.Add(Task<int>.Factory.StartNew(action, threadNumber)); 

Task.WaitAll(tasks.ToArray()); 
timer.Stop(); 

var threaderTimer = timer.ElapsedMilliseconds; 



Array.ForEach(Directory.GetFiles(@"c:\temp\output\"), File.Delete); 

timer.Reset(); 
timer.Start(); 
var entries = ZipFile.Open(@"c:\temp\temp.zip", ZipArchiveMode.Read).Entries; 
foreach (var entry in entries) 
{ 
    Console.WriteLine($"Extracting {entry.Name} via thread 1"); 
    entry.ExtractToFile([email protected]"C:\temp\output\{entry.Name}"); 
} 
timer.Stop(); 

Console.WriteLine($"Threaded version took: {threaderTimer} ms"); 
Console.WriteLine($"Non-Threaded version took: {timer.ElapsedMilliseconds} ms"); 


Console.ReadLine(); 
+2

これは答えではなく、コメントによく合っています – Simsons

+0

"ファイルを抽出するために他の方法がありますか?それを行うためのより良い方法とその理由を説明していることは間違いありません。 – rolls

+0

どのように "それは説明する"?私は問題が3つのapprocahesを言及しているとあなたは答え(コメント)は、それらのいずれかを参照し、アプローチのいずれかで明確ではない。どのようにthosel 3よりも異なっていて、それは良いですか? – Simsons

2

ZipFile is explicitly documented as not guaranteed to be threadsafe for instance members恩恵を受けるものではないという点を証明したzipアーカイブに応じて、額面上/実行速度は低下します。したがって、あなたがやろうとしていることは、このライブラリではできません。 いくつかの他のライブラリがzipファイルごとに複数のスレッドをサポートしていますが、私はそれを期待しません。

複数のファイルを同時に解凍するのにマルチスレッドを使用できますが、ではなく、同じzipファイル内に複数のエントリがある場合はです。

関連する問題