我有一个将文件读入内存的应用程序。然后,在将结果呈现给用户之前,它会对此数据进行大量检查(检查在后台同时在多个任务上运行)。
虽然应用程序没有因OutOfMemory异常而崩溃,但我注意到即使处理在后台进行,UI也会挂起,窗口会突然变为“Not Responding”。应用程序最终将完成任务并显示结果,但同时应用程序看起来已损坏,并会提示用户关闭窗口。
经过大量谷歌搜索后,我不确定如何处理这个问题。有什么我应该做的,以确保应用程序不会变得没有反应?我是否应该尝试计算启动时估计的内存可用性与使用情况,并在机器没有足够RAM时提醒用户?如果可用内存低于一定数量,我应该监控内存使用情况吗?
为了清楚起见,所有处理都在后台进行。我正在使用Task.Run
。我想我可以告诉UI线程没有被处理阻止,因为当我在具有足够内存的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());
}
屏幕截图
答案 0 :(得分:0)
1)您有一些访问磁盘的IO密集型进程 2)在此阶段,您的用户界面无响应。
3)您的用户界面 有另一个要求,以显示动画图像 进程正在运行 的更新强>
4)测试表明主要问题是缺少物理内存,这会强制系统交换到页面文件,而新的顺序IO绑定准并发请求。
这会导致口吃。
我建议使用以下方法将UI与IO进程分离,使其无响应
首先测试它,然后运行您的实际任务
的更新强>
在目前的情况下,单独使用异步方法无法解决问题
用户界面因为系统而口吃
因此,新提出的解决方案:
使用一段(或多段)虚拟内存访问大文件
这是使用MemoryMappedFile来实现的,它将文件和内存空间相关联,让映射的部分被视为主内存。
文件读取和写入操作以相同的方式执行,但通过MemoryMappedViewAccessor(随机访问)或MemoryMappedViewStream(顺序访问)执行。
当相关的访问者为Flushed()时,执行写操作
可以共享访问者。进程可以在没有并发的情况下访问同一个Accessor。
类MyTaskResults用于存储结果并将参数传递给负责IO绑定进程的异步方法。
修改1:
返回的值必须是List<string>
(?)
(为什么在返回的列表上使用.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()
,这不一定是异步方法。
<强> EDIT2:强>
将来电移至TaskRunProxy()
事件处理程序中的MainWindow.Loaded()
private void wMain_Loaded(object sender, RoutedEventArgs e) { TaskRunProxy(); }
添加了TextDirPath
,ImagesDirPath
,NativesDirPath
的搜索目录。
结果:
TextFiles:765(
*.txt
) - ImageFiles:697(*.jpg
) - NativeFiles:28422(*.dll
)
经历时间:88428ms
初始:页面文件大小:4096内存(工作集):60.907.520
最终:页面文件大小:4096内存(工作集):91.385.856
总计:找到29884个与提供的模式匹配的文件。
总内存:30.478.336(~29Mb)
用户界面甚至没有注意到它。
=&GT;使用最大内存时,系统可能会大量交换。归零 - 重启 - 重建系统页面文件(以及常规清理/碎片整理)后,应该进行测试。
<强> EDIT3:强>
创建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
}
用于运行(可能是同步的)任务的异步方法:
编辑1:
返回值已更改为List<string>
的 EDIT2:强>
使用“真实”文件枚举+内存加载更改虚拟Thread.Sleep(x):
Directory.GetFiles(_Task.[PATH], "[PATTERN]", SearchOption.AllDirectories).ToList<string>();
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;
}