我对C#和编码一般还是比较陌生,因此其中某些内容可能会以错误的方式处理问题。我编写的程序可以正常工作并按预期压缩文件,但是如果源文件很大,则该程序似乎(对于Windows)挂起。我觉得我应该使用a,Thread
但不确定是否会有所帮助。
我会使用进度条,但是System.IO.Compression
从中替换的zipfile的“新”(.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?
我会使用进度条,但是
System.IO.Compression
从中替换的zipfile的“新”(.net 4.5)库Ionic.Zip.ZipFile
没有报告进度的方法吗?有没有解决的办法?我应该使用Thread
吗?还是DoWork
?
您这里确实有两个问题:
ZipFile
该类的.NET版本不包括进度报告。CreateFromDirectory()
方法将阻塞,直到创建了整个存档为止。我对Ionic / DotNetZip库不是很熟悉,但是浏览文档时,我看不到任何从目录创建存档的异步方法。因此,无论如何,#2都是一个问题。解决该问题的最简单方法是在后台线程中运行工作,例如使用Task.Run()
。
至于#1问题,我不会将.NETZipFile
类描述为已替换了Ionic库。是的,它是新的。但是.NET在以前的版本中已经具有.zip存档支持。只是没有像这样的便利班ZipFile
。而且,既不支持.zip存档,也不ZipFile
提供“开箱即用”的进度报告。因此,它们本身都无法真正取代Ionic DLL。
因此,恕我直言,在我看来,如果您正在使用Ionic DLL并且对您有用,那么最好的解决方案就是继续使用它。
如果您真的不想使用它,那么您的选择将受到限制。.NETZipFile
只是无法满足您的需求。您可以做一些骇人听闻的事情,以解决缺乏功能的问题。对于编写档案,您可以估计压缩后的大小,然后监视正在写入的文件大小,并根据该大小计算估计的进度(即每秒在一个单独的异步任务中轮询文件大小)。为了提取档案,您可以监视正在生成的文件,并以此方式计算进度。
但归根结底,这种方法远非理想。
另一种选择是通过使用ZipArchive
基于较早版本的功能,自己亲自编写存档并跟踪从源文件读取的字节来监视进度。为此,您可以编写一个Stream
包装实际输入流的实现,并在读取字节时提供进度报告。
这是一个简单的示例,它Stream
看起来可能是什么样子(请注意关于注释的说明,以示说明……最好委托所有的虚方法,而不只是委派两个虚方法):
注意:在寻找与此问题相关的现有问题的过程中,我发现了一个本质上是重复的问题,只是它要求的是VB.NET答案而不是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);
}
}
}
}
笔记:
BasicProgress<T>
(见下文)的类。我在控制台程序中测试了代码,内置Progress<T>
类将使用线程池执行ProgressChanged
事件处理程序,这又可能导致进度报告乱序。在BasicProgress<T>
简单地直接调用处理程序,避免了这个问题。在使用的GUI程序中Progress<T>
,事件处理程序的执行将按顺序分派到UI线程。恕我直言,仍然应该BasicProgress<T>
在库中使用同步,但是UI程序的客户端代码可以很好地使用Progress<T>
(事实上,这可能是更好的选择,因为它在那里代表您处理跨线程分派)。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")));
}
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句