进度栏不适用于zipfile?程序似乎挂起时如何提供反馈

帝斯曼涡轮增压四驱

我对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

您这里确实有两个问题:

  1. ZipFile该类的.NET版本不包括进度报告。
  2. 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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章