【问题标题】:Application Maxing Memory应用程序最大化内存
【发布时间】:2018-01-18 17:03:52
【问题描述】:

我有一个将文件读入内存的应用程序。然后,在将结果呈现给用户之前,它会对该数据进行多次检查(在后台同时对多个任务进行检查)。

虽然应用程序没有因为 OutOfMemory 异常而崩溃,但我注意到即使处理是在后台进行的,UI 也会出现问题,并且窗口会突然“无响应”。应用程序最终会完成任务并显示结果,但与此同时,应用程序看起来已损坏并会提示用户关闭窗口。

经过大量谷歌搜索后,我不确定如何处理这个问题。为了确保应用程序不会无响应,我应该做些什么吗?我是否应该尝试计算估计的内存可用性与启动时的使用情况,并在机器没有足够 RAM 时提醒用户?如果可用内存低于一定数量,我应该监控内存使用情况并采取措施吗?

需要明确的是,所有处理都在后台进行。我正在使用Task.Run。我想我可以说 UI 线程没有被处理阻塞,因为当我在具有足够内存的机器上运行时,UI 不会出现问题。

片段

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    TaskList();
}

private async Task TaskList()
{
var taskList = new List<Task>();
txbStatus.Text += "Processing...\n";

taskList.Add(Task.Run(async () =>
{
        await GetFileLists();

    Application.Current.Dispatcher.Invoke(() => { txbStatus.Text += "Get file list completed\n"; });
}));

await Task.WhenAll(taskList.ToArray());
}

private async Task GetFileLists()
{
    var taskList = new List<Task>();

    if (!string.IsNullOrEmpty(TextDirPath))
        taskList.Add(Task.Run(() =>
        {
            _textFilesInFolderPathList =
                FileList.GetFileList(TextDirPath);
        }));
    if (!string.IsNullOrEmpty(ImagesDirPath))
        taskList.Add(Task.Run(() =>
        {
            _imageFilesInFolderPathList =
                FileList.GetFileList(ImagesDirPath));
        }));

    if (!string.IsNullOrEmpty(NativesDirPath))
        taskList.Add(Task.Run(() =>
        {
            _nativesFilesInFolderPathList =
                FileList.GetFileList(NativesDirPath);
        }));

    await Task.WhenAll(taskList.ToArray());
}

屏幕截图

【问题讨论】:

  • 不要在 UI 线程上工作。启动另一个线程来处理繁重的任务。 UI 线程负责保持应用程序响应,让另一个线程来执行您的其他任务。你说处理是在后台进行的,这是否意味着你已经在另一个线程上?
  • @EMUEVIL 就像我说的,一切都在后台发生。我正在使用Task.Run。在具有更多内存的机器上,用户界面不会出现问题。
  • "为了清楚起见,所有的处理都在后台进行。"显然不是。您的 UI 线程已冻结,因为您在该 UI 线程中执行了长时间运行的工作。你不需要这样做。
  • 所以,它是 WPF。第一个await Task.WhenAll(taskList.ToArray()) 不清楚在等待什么上下文。您需要将该任务添加到列表中吗?你能await Task.Run(async () =&gt; (...)) 吗?并在返回后更新 UI?实际的工人,消耗的方法,似乎是FileList.GetFileList(),如果这是一个内存消耗的问题,但你没有显示它。事实上,它看起来像一个未正确设置的异步/等待上下文。第一个sn-p中的代码是什么调用?
  • 内存使用率很高(因为您只有 2 Gb 的 RAM),但我认为这不是您的问题。如果正在运行的任务需要更多内存,系统可以回收一些内存。我正在切换到回答模式(我需要更多空间来写下一些东西),让我们看看我们是否可以解决这个问题。

标签: c# memory


【解决方案1】:

1) 您有一些访问磁盘的 IO 密集型进程。
2) 您的 UI 在此阶段变得无响应。
3) 您的 UI 还有另一个要求,显示动画图像,而那些 进程正在运行。
更新:
4) 测试表明主要问题是缺少物理内存,这迫使系统交换到页面文件,同时发出新的顺序 IO 绑定准并发请求。
这会导致卡顿。

我建议使用以下方法将 UI 从 IO 进程中分离出来,使其无响应。
首先对其进行测试,然后运行您的实际任务。
更新:
在当前情况下,仅靠 Async 方法无法解决问题。
UI 卡顿是因为系统卡顿了。

因此,新提出的解决方案:
使用一段(或多段)虚拟内存访问大文件。
这是使用MemoryMappedFile 实现的,它将文件和内存空间关联起来,让映射的部分被视为主内存。 文件读取和写入操作以相同的方式执行,但通过MemoryMappedViewAccessor(随机访问)或MemoryMappedViewStream(顺序访问)。 当相关的Accessor为Flushed()时,执行一次写操作
可以共享访问器。进程可以在没有并发的情况下访问同一个 Accessor。


MyTaskResults 类用于存储结果并将参数传递给负责 IO 绑定进程的异步方法。

编辑1:
返回值必须是List&lt;string&gt; (?)
(为什么在返回的 List 上使用.FirstOrDefault())?

编辑 3:
向主类添加了两个共享的 MemoryMappedFile 对象。

public class MyTaskResults
{
    public int TaskID { get; set; }
    public string TextDirPath { get; set; }
    public string ImagesDirPath { get; set; }
    public string NativesDirPath { get; set; }

    public List<string> TextDirPathResult { get; set; }
    public List<string> ImagesDirPathResult { get; set; }
    public List<string> NativesDirPathResult { get; set; }
}

//List of MyTaskResults Class, used to store all Tasks run and their results
List<MyTaskResults> _ListOfTasks = new List<MyTaskResults>();

private static MemoryMappedFile _mmfDatData; 
private static MemoryMappedFile _mmfOptData;

bool CriticalJobRunning = false;
int _TasksCounter = 0;

您可以通过任何其他方法运行 TaskRunProxy(),而不必是异步方法。

编辑2:
MainWindow.Loaded() 事件处理程序中将调用移至TaskRunProxy()

private void wMain_Loaded(object sender, RoutedEventArgs e) { TaskRunProxy(); }

添加了TextDirPathImagesDirPathNativesDirPath 的搜索目录。
结果:

文本文件:765 (*.txt) - 图像文件:697 (*.jpg) - 本机文件:28422 (*.dll)

经过时间:88428 毫秒
初始:页面文件大小:4096 内存(工作集):60.907.520
最终:页面文件大小:4096 内存(工作集):91.385.856

总计:找到与提供的模式匹配的 29884 个文件。
总内存:30.478.336 (~29Mb)

用户界面甚至没有注意到它。
=> 内存已用完,系统可能会进行大量交换。应在清零-重启-重建系统页面文件(以及一般清理/碎片整理)之后执行测试。

编辑3:
创建 2 个内存映射文件,将大磁盘文件与虚拟内存空间相关联:

public MainWindow()
{
    InitializeComponent();

    string _datFilePath = @"PATHTOLARGEFILE";//~200MB
    string _optFilePath = @"PATHTOLARGEFILE2";//~200MB

    Int64 _sizeDatData = new FileInfo(_datFilePath).Length;
    Int64 _sizeOptData = new FileInfo(_optFilePath).Length;

    //Capacity = 0 means a capacity equal to the full size of the file on disk. 
    //Or _sizeDatData and _sizeOptData can be used.
    _mmfDatData = MemoryMappedFile.CreateFromFile(_datFilePath, 
                                    FileMode.Open, 
                                    "DatData", 0, 
                                    MemoryMappedFileAccess.ReadWrite);
    _mmfOptData = MemoryMappedFile.CreateFromFile(_optFilePath, 
                                    FileMode.Open, 
                                    "OptData", 0, 
                                    MemoryMappedFileAccess.ReadWrite);
}


读写操作:(随机访问)
[TYPE] 可以是引用类型或值类型(当然包括结构)
该示例将前 128 MB 用于读/写操作。

    MemoryMappedViewAccessor _viewOptData = _mmfOptData.CreateViewAccessor(
                             0, 
                             0x8000000L, 
                             MemoryMappedFileAccess.ReadWrite);

    _viewOptData.Read<[TYPE]>([Position], out [TYPE]);
    _viewOptData.Write<[TYPE]>([Position], ref [TYPE]);


呈现 UI 后运行 IO 绑定任务。

private void wMain_Loaded(object sender, RoutedEventArgs e)
{
    TaskRunProxy();
}


private async void TaskRunProxy()
{
    _TasksCounter += 1;

    MyTaskResults _Task = new MyTaskResults
    {
        TaskID = _TasksCounter,
        TextDirPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
        ImagesDirPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
        NativesDirPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows)
    };

        Console.WriteLine("Page File Size: " + Environment.SystemPageSize.ToString());
        Console.WriteLine("Memory (Working Set): " + Environment.WorkingSet.ToString());

        Stopwatch _SW = new Stopwatch();
        _SW.Start();
        CriticalJobRunning = true;
        _ListOfTasks.Add(await GetFileListsAsync(_Task));
        CriticalJobRunning = false;
        _SW.Stop();

        Console.WriteLine("Time: " + _SW.ElapsedMilliseconds + Environment.NewLine);

        Console.WriteLine("TextFiles: " +  _Task.TextDirPathResult.Count + 
                        "  ImageFiles: " + _Task.ImagesDirPathResult.Count + 
                        "  NativeFiles: " + _Task.NativesDirPathResult.Count);

        Console.WriteLine("Page File Size: " + Environment.SystemPageSize.ToString());
        Console.WriteLine("Memory (Working Set): " + Environment.WorkingSet.ToString());
}

private void wMain_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    if (this.CriticalJobRunning)
        e.Cancel = true;
    //Let the use know
}

用于运行(可能是同步的)任务的异步方法:

Edit1:
返回值更改为List&lt;string&gt;
Edit2:
使用“真实”文件枚举 + 内存加载更改了虚拟 Thread.Sleep(x):

Directory.GetFiles(_Task.[PATH], "[PATTERN]", SearchOption.AllDirectories).ToList&lt;string&gt;();

private async Task<MyTaskResults> GetFileListsAsync(MyTaskResults _Task)
{
    if (!string.IsNullOrEmpty(_Task.TextDirPath))
        _Task.TextDirPathResult = await Task.Run(() =>
        {
            return Directory.GetFiles(_Task.TextDirPath, 
                                     "*.txt", 
                                      SearchOption.AllDirectories).ToList<string>();
            //Thread.Sleep(4000);
            //return new List<string> {TextDirPathResult Completed"};
            //return FileList.GetFileList(_Task.TextDirPath);
        });

    if (!string.IsNullOrEmpty(_Task.ImagesDirPath))
        _Task.ImagesDirPathResult = await Task.Run(() =>
        {
            return Directory.GetFiles(_Task.ImagesDirPath, 
                                     "*.jpg", 
                                      SearchOption.AllDirectories).ToList<string>();
            //Thread.Sleep(3000);
            //return new List<string> {"TextDirPathResult Completed"};
            //return FileList.GetFileList(_Task.ImagesDirPath);
        });

    if (!string.IsNullOrEmpty(_Task.NativesDirPath))
        _Task.NativesDirPathResult = await Task.Run(() =>
        {
            return Directory.GetFiles(_Task.NativesDirPath, 
                                     "*.dll", 
                                      SearchOption.AllDirectories).ToList<string>();
            //Thread.Sleep(3000);
            //return new List<string> {"TextDirPathResult Completed"};
            //return FileList.GetFileList(_Task.NativesDirPath);
        });

    return _Task;
}

【讨论】:

  • 我将其转换为 WPF 应用程序并运行,发现没有无响应。然后,为了进一步模拟我的应用程序,我读取了我的应用程序在内存中的两个非常大的文件,这会最大限度地使用 2GB 内存。这确实会导致上述代码在完成之前出现问题并在一段时间内无响应。
  • 对于附加信息,我还更新了路径,因此它实际上是针对已填充的文件夹运行的。在内存中有文件的情况下启动 TaskRunProxy 时,Task Mgr 列出物理内存;总计:2047;缓存:166;可用:165;免费0。
  • 如果有帮助,我可以通过 PasteBin 向您提供更新代码?
  • @windowskm 为什么不呢。粘贴走。 -- 如果你在短期内最大化你的内存,内存交换可能会导致这种情况,因此调整系统交换文件设置可能会有所帮助。但这之后。
  • 那些内存结果很不一样:之前1.2G,之后680Mb,之前32Mb,之后700Mb?但是,很明显,内存变得稀缺:)。您是否可以以Memory Mapped Files) 的身份访问这些大文件?性能应该会提高,尤其是在这种情况下。此外,您是否在文件加载过程之间延迟(10~15 秒)测试了结果。和目录枚举过程。? (顺便说一句,我在 WPF 项目中测试了该代码)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
相关资源
最近更新 更多