C# - WPF在分析大文件时没有响应

时间:2018-01-17 14:53:01

标签: c# wpf memory readline freeze

我的Windows窗体应用程序读取一个大的.txt - 文件(1.6 GB)并分析文本,例如: G。它会比较if - 条件中的某些值。

当读取了约40%的文档时,应用程序不再响应,并且不再更新其progess栏。它不会抛出OutOfMemory-Exeption或其他任何东西,当分析完成时,应用程序会响应用户。

有没有办法避免这种令人不快的冻结,例如: G。通过为我的应用程序分配更多内存?

代码示例(line和其他变量已声明为global):

l = File.ReadLines(@path).Count();
ProgressBar.Maximum = l;

int counter = 0;

while ((line = reader.ReadLine()) != null)
{
    ProgressBar.Value += 1;
    if (line.Contains(date) && (DateTime.Compare(DateTime.Parse(start), DateTime.Parse(line.Substring(19, 8))))
    {
        if (line.Contains("abcdef"))
        {
            counter++;
        }
    }
}

1 个答案:

答案 0 :(得分:0)

理想情况下,您应该在其他一些线程上异步读取这些行。这样,您就不会阻止UI线程,冻结您的应用程序。

我对内置async I / O API的一个问题是粒度:您可以轻松地异步读取单个行或整个文件。如果你想处理线而不是块,那么这些是你唯一的选择。这是不幸的,因为将每一行作为单独的async操作读取对于大文件来说花费的时间太长,并且一次读取整个文件可能会非常耗费内存。

能够分批读取行(以合理的速度),同时让您有机会处理到目前为止已读取的内容(合理的内存使用情况)。这非常难以实现:

public static class AsyncFile
{
    // Tweak as you see fit.
    private const int MaxLinesPerBatch = 1000;

    private const FileMode ReadMode = FileMode.Open;
    private const FileAccess ReadAccess = FileAccess.Read;
    private const FileShare ReadShare = FileShare.Read;

    public static async Task ReadAllLinesAsync(
        string path,
        Action<string> lineCallback,
        Action<double?> progress = null,
        Encoding encoding = null)
    {
        using (var stream = new FileStream(path, ReadMode, ReadAccess, ReadShare))
        using (var reader = new StreamReader(stream, encoding ?? Encoding.UTF8))
        {
            var fileSize = stream.CanSeek ? stream.Length : default(long?);
            var currentProgress = fileSize < 1L ? 0d : 0d / fileSize;

            progress?.Invoke(currentProgress);

            var lines = new string[MaxLinesPerBatch];
            int linesRead;

            var batchFunction = CreateBatchReadFunction(reader, lines);

            while ((linesRead = await Task.Run(batchFunction)) > 0)
            {
                for (var i = 0; i < linesRead; i++)
                    lineCallback(lines[i]);

                if (currentProgress == null)
                    continue;

                var newProgress = ((100L * stream.Position) / fileSize) / 100d;
                if (newProgress > currentProgress)
                    progress?.Invoke(currentProgress = newProgress);
            }

            if (currentProgress < 1d)
                progress?.Invoke(1d);
        }
    }

    private static Func<int> CreateBatchReadFunction(
        StreamReader reader,
        string[] lines)
    {
        return () =>
               {
                   string line;

                   var count = 0;

                   while (count < lines.Length && (line = reader.ReadLine()) != null)
                       lines[count++] = line;

                   return count;
               };
    }
}

读取一批行时,每行调用一次lineCallback。如果提供,progress会将当前进度报告为double0.0之间的1.0,您可以将其乘以进度条的Maximum。如果无法提前以低成本确定进度,则会报告null,您可以通过将进度条的IsIndeterminate设置为true来表示。

除非另行配置,否则将在调用者的上下文中调用lineCallbackprogress,因此从任何一个更新UI都是安全的。但是,我会避免在lineCallback内做大量工作。

用法很简单:

await AsyncFile.ReadAllLinesAsync(
    PATH_TO_FILE,
    line => { /* do something with line*/  },
    progress =>
    {
        if (progress >= 0d)
            progressBar.Value = progress.Value * progressBar.Maximum;
        else
            progressBar.IsIndeterminate = true;
    });