我对C#和编码尚不陌生,因此其中某些内容可能会以错误的方式处理问题。我编写的程序可以正常工作并按预期压缩文件,但是如果源文件很大,则该程序似乎(对于Windows)挂起。我觉得我应该使用Thread
,但不确定是否会有所帮助。
我将使用进度条,但使用System.IO.Compression
中的zipfile的"new"(.net 4.5)库替换了Ionic.Zip.ZipFile
没有报告进度的方法吗?有没有解决的办法?我应该使用Thread
吗?或DoWork
?
问题在于用户和系统无法获得程序正在执行的反馈。
我不确定我是否以正确的方式提出这个问题。
下面是正在运行的代码,但同样,它似乎会挂起系统。
private void beginBackup_Click(object sender, EventArgs e)
{
try
{
long timeTicks = DateTime.Now.Ticks;
string zipName = "bak" + timeTicks + ".zip";
MessageBox.Show("This Will take a bit, there is no status bar :(");
ZipFile.CreateFromDirectory(Properties.Settings.Default.source,
Properties.Settings.Default.destination + "\\" + zipName);
MessageBox.Show("Done!");
this.Close();
}
catch (IOException err)
{
MessageBox.Show("Something went wrong" + System.Environment.NewLine
+ "IOException source: {0}", err.Source);
}
}
重要的一行是:
`ZipFile.CreateFromDirectory(Properties.Settings.Default.source,
Properties.Settings.Default.destination + "\\" + zipName);`
编辑
ZipFile.CreateFromDirectory()
没有遍历目录,因此没有要增加的内容吗?它只会在没有报告的情况下开始和结束。除非我弄错了?在此使用此方法:
while (!completed)
{
// your code here to do something
for (int i = 1; i <= 100; i++)
{
percentCompletedSoFar = i;
var t = new Task(() => WriteToProgressFile(i));
t.Start();
await t;
if (progress != null)
{
progress.Report(percentCompletedSoFar);
}
completed = i == 100;
}
}
for循环中的代码只能运行一次,因为Zipfile woudl仍会挂起程序,那么进度条会立即从0变为100?
最佳答案
I would use a progress bar but the 'new' (.net 4.5) library for zipfile from
System.IO.Compression
which replacedIonic.Zip.ZipFile
does not have a method to report progress? Is there a way around this? Should I be using aThread
? orDoWork
?
您这里确实有两个问题:
ZipFile
方法将阻塞,直到创建了整个存档为止。 我对Ionic/DotNetZip库不是很熟悉,但是浏览文档时,我看不到任何用于从目录创建文件的异步方法。因此,无论如何,#2都是一个问题。解决它的最简单方法是在后台线程中运行工作,例如使用
CreateFromDirectory()
。至于#1问题,我不会将.NET
Task.Run()
类描述为已替换了Ionic库。是的,它是新的。但是.NET在以前的版本中已经具有.zip存档支持。只是不是ZipFile
这样的便利类。而且,早期对.zip归档文件和ZipFile
的支持都没有“开箱即用”的进度报告。因此,它们本身都无法真正取代Ionic DLL。所以恕我直言,在我看来,如果您正在使用Ionic DLL并且对您有用,那么最好的解决方案就是继续使用它。
如果您真的不想使用它,那么您的选择将受到限制。 .NET
ZipFile
只是不执行您想要的操作。您可以做一些骇人听闻的事情,以解决缺乏功能的问题。对于编写文件,您可以估算压缩后的大小,然后监视正在写入的文件大小,并根据该大小计算估算的进度(即每秒在一个单独的异步任务中轮询文件大小)。为了提取文件,您可以监视正在生成的文件,并以此方式计算进度。但归根结底,这种方法远非理想。
另一种选择是使用较旧的基于
ZipFile
的功能来监视进度,自己明确编写存档并跟踪从源文件读取的字节。为此,您可以编写ZipArchive
实现,该实现包装实际的输入流,并在读取字节时提供进度报告。这是
Stream
可能看起来像的一个简单示例(为说明起见,请注意注释……实际上最好委派所有虚拟方法,而不仅仅是委托(delegate)您使用的两个方法):注意:在寻找与此问题相关的现有问题的过程中,我发现了一个本质上是重复的问题,除了it's asking for a VB.NET answer instead of C#之外。除创建存档外,它还要求从存档中提取文件时进行进度更新。因此,我在这里针对VB.NET修改了答案,添加了提取方法,并对实现进行了一些调整。我更新了下面的答案以合并这些更改。
StreamWithProgress.cs
class StreamWithProgress : Stream
{
// NOTE: for illustration purposes. For production code, one would want to
// override *all* of the virtual methods, delegating to the base _stream object,
// to ensure performance optimizations in the base _stream object aren't
// bypassed.
private readonly Stream _stream;
private readonly IProgress<int> _readProgress;
private readonly IProgress<int> _writeProgress;
public StreamWithProgress(Stream stream, IProgress<int> readProgress, IProgress<int> writeProgress)
{
_stream = stream;
_readProgress = readProgress;
_writeProgress = writeProgress;
}
public override bool CanRead { get { return _stream.CanRead; } }
public override bool CanSeek { get { return _stream.CanSeek; } }
public override bool CanWrite { get { return _stream.CanWrite; } }
public override long Length { get { return _stream.Length; } }
public override long Position
{
get { return _stream.Position; }
set { _stream.Position = value; }
}
public override void Flush() { _stream.Flush(); }
public override long Seek(long offset, SeekOrigin origin) { return _stream.Seek(offset, origin); }
public override void SetLength(long value) { _stream.SetLength(value); }
public override int Read(byte[] buffer, int offset, int count)
{
int bytesRead = _stream.Read(buffer, offset, count);
_readProgress?.Report(bytesRead);
return bytesRead;
}
public override void Write(byte[] buffer, int offset, int count)
{
_stream.Write(buffer, offset, count);
_writeProgress?.Report(count);
}
}
有了它,使用
Stream
监视进度来显式地处理存档创建相对简单:ZipFileWithProgress.cs
static class ZipFileWithProgress
{
public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, IProgress<double> progress)
{
sourceDirectoryName = Path.GetFullPath(sourceDirectoryName);
FileInfo[] sourceFiles =
new DirectoryInfo(sourceDirectoryName).GetFiles("*", SearchOption.AllDirectories);
double totalBytes = sourceFiles.Sum(f => f.Length);
long currentBytes = 0;
using (ZipArchive archive = ZipFile.Open(destinationArchiveFileName, ZipArchiveMode.Create))
{
foreach (FileInfo file in sourceFiles)
{
// NOTE: naive method to get sub-path from file name, relative to
// input directory. Production code should be more robust than this.
// Either use Path class or similar to parse directory separators and
// reconstruct output file name, or change this entire method to be
// recursive so that it can follow the sub-directories and include them
// in the entry name as they are processed.
string entryName = file.FullName.Substring(sourceDirectoryName.Length + 1);
ZipArchiveEntry entry = archive.CreateEntry(entryName);
entry.LastWriteTime = file.LastWriteTime;
using (Stream inputStream = File.OpenRead(file.FullName))
using (Stream outputStream = entry.Open())
{
Stream progressStream = new StreamWithProgress(inputStream,
new BasicProgress<int>(i =>
{
currentBytes += i;
progress.Report(currentBytes / totalBytes);
}), null);
progressStream.CopyTo(outputStream);
}
}
}
}
public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, IProgress<double> progress)
{
using (ZipArchive archive = ZipFile.OpenRead(sourceArchiveFileName))
{
double totalBytes = archive.Entries.Sum(e => e.Length);
long currentBytes = 0;
foreach (ZipArchiveEntry entry in archive.Entries)
{
string fileName = Path.Combine(destinationDirectoryName, entry.FullName);
Directory.CreateDirectory(Path.GetDirectoryName(fileName));
using (Stream inputStream = entry.Open())
using(Stream outputStream = File.OpenWrite(fileName))
{
Stream progressStream = new StreamWithProgress(outputStream, null,
new BasicProgress<int>(i =>
{
currentBytes += i;
progress.Report(currentBytes / totalBytes);
}));
inputStream.CopyTo(progressStream);
}
File.SetLastWriteTime(fileName, entry.LastWriteTime.LocalDateTime);
}
}
}
}
笔记:
Stream
的类(请参见下文)。我在控制台程序中测试了代码,内置的BasicProgress<T>
类将使用线程池执行Progress<T>
事件处理程序,这又可能导致进度报告困惑。 ProgressChanged
只是直接调用处理程序,从而避免了该问题。在使用BasicProgress<T>
的GUI程序中,事件处理程序的执行将按顺序分派(dispatch)到UI线程。恕我直言,仍然应该在库中使用同步Progress<T>
,但是使用BasicProgress<T>
可以很好地使用UI程序的客户端代码(实际上,这可能是更好的选择,因为它代表您在那里处理跨线程分派(dispatch))。 BasicProgress.cs
class BasicProgress<T> : IProgress<T>
{
private readonly Action<T> _handler;
public BasicProgress(Action<T> handler)
{
_handler = handler;
}
void IProgress<T>.Report(T value)
{
_handler(value);
}
}
当然,还有一个测试所有程序的程序:
Program.cs
class Program
{
static void Main(string[] args)
{
string sourceDirectory = args[0],
archive = args[1],
archiveDirectory = Path.GetDirectoryName(Path.GetFullPath(archive)),
unpackDirectoryName = Guid.NewGuid().ToString();
File.Delete(archive);
ZipFileWithProgress.CreateFromDirectory(sourceDirectory, archive,
new BasicProgress<double>(p => Console.WriteLine($"{p:P2} archiving complete")));
ZipFileWithProgress.ExtractToDirectory(archive, unpackDirectoryName,
new BasicProgress<double>(p => Console.WriteLine($"{p:P0} extracting complete")));
}
}
关于c# - 进度栏不适用于zipfile?程序似乎挂起时如何给出反馈,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42430559/