进度栏不适用于zipfile?程序似乎挂起时如何给出反馈 [英] Progress Bar not available for zipfile? How to give feedback when program seems to hang

查看:51
本文介绍了进度栏不适用于zipfile?程序似乎挂起时如何给出反馈的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对C#和编码一般还是比较陌生,所以其中有些可能会以错误的方式处理问题.我编写的程序可以正常工作并按预期压缩文件,但是如果源很大,则该程序似乎(对于Windows)挂起.我觉得我应该使用Thread,但不确定是否会有所帮助.

I am fairly new to C# and coding in general so some of this might be going about things the wrong way. The program I wrote works and compresses the file as expected, but if the source is rather large, the program appears (to Windows) to hang. I feel like I should be using a Thread but I am not sure that will help.

我会使用进度条,但是替换了System.IO.Compression中zip文件的'new'(.net 4.5)库 Ionic.Zip.ZipFile没有报告进度的方法?有没有解决的办法?我应该使用Thread吗?还是DoWork?

I would use a progress bar but the 'new' (.net 4.5) library for zipfile from System.IO.Compression which replaced Ionic.Zip.ZipFile does not have a method to report progress? Is there a way around this? Should I be using a Thread? or DoWork?

问题在于用户和系统无法获得有关程序运行情况的反馈.

The trouble is that the user and the system is not getting feedback on what the program is doing.

我不确定我是否以正确的方式提出了这个问题. 下面是正在运行的代码,但同样,它似乎会挂起系统.

I am not sure I am asking the question the right way. Below is the code that is working, but again, will appear to hang the system.

    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);`

EDIT

EDIT

ZipFile.CreateFromDirectory()不在目录中,因此没有要增加的内容吗?它只会在没有报告的情况下开始和结束.除非我弄错了?

ZipFile.CreateFromDirectory()is not walking the directory so there is nothing to increment? it would simply start and finish with no reporting. Unless I am mistaken?

在此使用此方法:

        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?

the code in the for loop would only run once, as the Zipfile woudl still hang the program, then the progress bar would immediately go from 0 to 100?

推荐答案

我会使用进度条,但是替换了Ionic.Zip.ZipFileSystem.IO.Compression中的zip文件的新"(.net 4.5)库没有报告进度的方法吗?有没有解决的办法?我应该使用Thread吗?还是DoWork?

I would use a progress bar but the 'new' (.net 4.5) library for zipfile from System.IO.Compression which replaced Ionic.Zip.ZipFile does not have a method to report progress? Is there a way around this? Should I be using a Thread? or DoWork?

您这里确实有两个问题:

You really have two issues here:

  1. .c版本的ZipFile类不包括进度报告.
  2. CreateFromDirectory()方法将阻塞,直到创建了整个存档为止.
  1. The .NET version of the ZipFile class does not include progress reporting.
  2. The CreateFromDirectory() method blocks until the entire archive has been created.

我对Ionic/DotNetZip库不是很熟悉,但是浏览文档时,我看不到任何用于从目录创建档案的异步方法.因此,无论如何,#2都是一个问题.解决它的最简单方法是在后台线程中运行工作,例如使用Task.Run().

I am not that familiar with the Ionic/DotNetZip library, but browsing the docs, I don't see any asynchronous methods for creating an archive from a directory. So #2 would be an issue regardless. The easiest way to solve it is to run the work in a background thread, e.g. using Task.Run().

对于#1问题,我不会将.NET ZipFile类描述为已替换了Ionic库.是的,它是新的.但是.NET在以前的版本中已经支持.zip存档.只是没有像ZipFile这样的便利类.而且,早期对.zip存档的支持和ZipFile都不提供现成"的进度报告.因此,它们本身都不能真正替换离子DLL.

As for the #1 issue, I would not characterize the .NET ZipFile class as having replaced the Ionic library. Yes, it's new. But .NET already had .zip archive support in previous versions. Just not a convenience class like ZipFile. And neither the earlier support for .zip archives nor ZipFile provide progress reporting "out-of-the-box". So neither really replace the Ionic DLL per se.

恕我直言,在我看来,如果您正在使用Ionic DLL且对您有用,那么最好的解决方案就是继续使用它.

So IMHO, it seems to me that if you were using the Ionic DLL and it worked for you, the best solution is to just keep using it.

如果您真的不想使用它,那么您的选择将受到限制. .NET ZipFile只是不执行您想要的操作.您可以做一些骇人听闻的事情,以解决缺乏功能的问题.对于编写档案,您可以估算压缩后的大小,然后监视正在写入的文件大小,并根据该大小计算估算的进度(即每秒在一个单独的异步任务中轮询文件大小).要提取档案,您可以监视正在生成的文件,并以此方式计算进度.

If you really don't want to use it, your options are limited. The .NET ZipFile just doesn't do what you want. There are some hacky things you could do, to work around the lack of feature. For writing an archive, you could estimate the compressed size, then monitor the file size as it's being written and compute an estimated progress based on that (i.e. poll the file size in a separate async task, every second or so). For extracting an archive, you could monitor the files being generated, and compute progress that way.

但是最终,这种方法远非理想.

But at the end of the day, that sort of approach is far from ideal.

另一种选择是通过使用较早的基于ZipArchive的功能来监视进度,自己明确编写存档并跟踪从源文件读取的字节.为此,您可以编写一个Stream实现,该实现包装实际的输入流,并在读取字节时提供进度报告.

Another option is to monitor the progress by using the older ZipArchive-based features, writing the archive yourself explicitly and tracking the bytes as they are read from the source file. To do this, you can write a Stream implementation that wraps the real input stream, and which provides progress reporting as the bytes are read.

这是Stream看起来像的一个简单示例(请注意注释只是出于说明的目的…最好将 all 委托给虚拟方法,而不仅仅是两个方法)要求):

Here's a simple example of what that Stream might look like (note comment about this being for illustration purposes…it really would be better to delegate all the virtual methods, not just the two you're required to):

注意:在寻找与此相关的现有问题的过程中,我发现一个本质上是重复的问题,除了

Note: in the course of looking for existing questions related to this one, I found one that is essentially a duplicate, except that it's asking for a VB.NET answer instead of C#. It also asked for progress updates while extracting from an archive, in addition to creating one. So I adapted my answer here, for VB.NET, adding the extraction method, and tweaking the implementation a little. I've updated the answer below to incorporate those changes.

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监视进度,显式地处理存档创建相对简单:

With that in hand, it's relatively simple to handle the archive creation explicitly, using that Stream to monitor the progress:

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>只是直接调用处理程序,从而避免了该问题.在使用Progress<T>的GUI程序中,事件处理程序的执行将按顺序分派到UI线程.恕我直言,仍然应该在库中使用同步BasicProgress<T>,但是使用Progress<T>可以使UI程序的客户端代码正常(实际上,这可能是更好的选择,因为它可以代表您处理跨线程分派在那里).
  • 在进行任何工作之前,这将计算文件长度的总和.当然,这会产生少量的启动成本.在某些情况下,仅报告已处理的总字节数就足够了,让客户端代码担心是否需要进行初始计数.
  • This uses a class called BasicProgress<T> (see below). I tested the code in a console program, and the built-in Progress<T> class will use the thread pool to execute the ProgressChanged event handlers, which in turn can lead to out-of-order progress reports. The BasicProgress<T> simply calls the handler directly, avoiding that issue. In a GUI program using Progress<T>, the execution of the event handlers would be dispatched to the UI thread in order. IMHO, one should still use the synchronous BasicProgress<T> in a library, but the client code for a UI program would be fine using Progress<T> (indeed, that would probably be preferable, since it handles the cross-thread dispatching on your behalf there).
  • This tallies the sum of the file lengths before doing any work. Of course, this incurs a slight start-up cost. For some scenarios, it might be sufficient to just report total bytes processed, and let the client code worry about whether there's a need to do that initial tally or not.

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);
    }
}

当然,还有一个用于测试所有内容的程序:

And of course, a little program to test it all:

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")));
    }
}

这篇关于进度栏不适用于zipfile?程序似乎挂起时如何给出反馈的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆