Kev*_*Lee 10 c# memory asp.net asp.net-mvc entity-framework
如果我只浏览应用程序上的某些页面,它大约为500MB.这些页面中的许多页面访问数据库,但此时,我只有大约几行用于10个表,大多数存储字符串和一些小于50KB的小图标.
当我下载文件时,会出现真正的问题.该文件大约为140MB,并存储为varbinary(MAX)数据库中的文件.内存使用量突然升至1.3GB,瞬间降至1GB.该操作的代码如下:
public ActionResult DownloadIpa(int buildId)
{
var build = _unitOfWork.Repository<Build>().GetById(buildId);
var buildFiles = _unitOfWork.Repository<BuildFiles>().GetById(buildId);
if (buildFiles == null)
{
throw new HttpException(404, "Item not found");
}
var app = _unitOfWork.Repository<App>().GetById(build.AppId);
var fileName = app.Name + ".ipa";
app.Downloads++;
_unitOfWork.Repository<App>().Update(app);
_unitOfWork.Save();
return DownloadFile(buildFiles.Ipa, fileName);
}
private ActionResult DownloadFile(byte[] file, string fileName, string type = "application/octet-stream")
{
if (file == null)
{
throw new HttpException(500, "Empty file");
}
if (fileName.Equals(""))
{
throw new HttpException(500, "No name");
}
return File(file, type, fileName);
}
Run Code Online (Sandbox Code Playgroud)
在我的本地计算机上,如果我什么都不做,内存使用量将保持在1GB.如果我然后返回导航到某些页面,它会回落到500MB.
在部署服务器上,无论我做什么,它在第一次下载后都会保持在1.6GB.我可以通过不断下载文件来增加内存使用量,直到它达到3GB,然后下降到1.6GB.
在每个控制器中,我都覆盖了这个Dispose()方法:
protected override void Dispose(bool disposing)
{
_unitOfWork.Dispose();
base.Dispose(disposing);
}
Run Code Online (Sandbox Code Playgroud)
这指的是:
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.Dispose();
}
}
_disposed = true;
}
Run Code Online (Sandbox Code Playgroud)
因此,每次处理控制器时都应该处理我的工作单元.我使用的是Unity,我使用Heirarchical Lifetime Manager注册了工作单元.
以下是Profiler的一些屏幕截图:



我相信这可能是问题,或者我走错了路.为什么要Find()使用300MB?
编辑:
库:
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
internal IDbContext Context;
internal IDbSet<TEntity> DbSet;
public Repository(IDbContext context)
{
Context = context;
DbSet = Context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> GetAll()
{
return DbSet.ToList();
}
public virtual TEntity GetById(object id)
{
return DbSet.Find(id);
}
public TEntity GetSingle(Expression<Func<TEntity, bool>> predicate)
{
return DbSet.Where(predicate).SingleOrDefault();
}
public virtual RepositoryQuery<TEntity> Query()
{
return new RepositoryQuery<TEntity>(this);
}
internal IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
List<Expression<Func<TEntity, object>>> includeProperties = null)
{
IQueryable<TEntity> query = DbSet;
if (includeProperties != null)
{
includeProperties.ForEach(i => query.Include(i));
}
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
return query.ToList();
}
public virtual void Insert(TEntity entity)
{
DbSet.Add(entity);
}
public virtual void Update(TEntity entity)
{
DbSet.Attach(entity);
Context.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete(object id)
{
var entity = DbSet.Find(id);
Delete(entity);
}
public virtual void Delete(TEntity entity)
{
if (Context.Entry(entity).State == EntityState.Detached)
{
DbSet.Attach(entity);
}
DbSet.Remove(entity);
}
}
Run Code Online (Sandbox Code Playgroud)
编辑2:
我为各种场景运行了dotMemory,这就是我得到的.

红色圆圈表示有时在一次访问时会发生多次上升和下降.蓝色圆圈表示下载40MB文件.绿色圆圈表示下载140MB文件.此外,在很多时候,即使页面立即加载,内存使用量也会持续增加几秒钟.
因为文件很大,所以它是在大对象堆上分配的,它是用gen2集合收集的(你在配置文件中看到,紫色块是大对象堆,你看到它在10秒后收集).
在生产服务器上,您的内存可能比本地计算机上的内存多得多.因为内存压力较小,所以集合不会频繁出现,这就解释了为什么它会增加更多的数字 - 在收集之前LOH上有几个文件.
如果在MVC和EF中的不同缓冲区中,一些数据也会在不安全的块中被复制,那么我不会感到惊讶,这解释了非托管内存增长(EF的薄尖峰,MVC的广阔平台)
最后,一个500MB的基线是一个大项目并不完全令人惊讶(疯狂!但是真的!)
所以回答你的问题为什么它使用如此多的内存非常可能是"因为它可以",或者换句话说,因为没有内存压力来执行gen2集合,并且下载的文件在大对象中未被使用堆直到集合驱逐它们,因为生产服务器上的内存很丰富.
这可能不是一个真正的问题:如果有更多的内存压力,会有更多的收集,你会看到更低的内存使用率.
至于如何应对,我担心你对实体框架不满意.据我所知,它没有流API.WebAPI确实允许按顺序传输响应,但是如果整个大对象都在内存中,这对你没有多大帮助(尽管它可能有助于某些人在MVC未经探索的部分中使用非托管内存) .
将 GC.Collect() 添加到 Dispose 方法中以进行测试。如果泄漏持续存在,则为真正的泄漏。如果它消失了,那只是 GC 延迟了。
你这样做了并说道:
@usr 内存使用量现在几乎没有达到 600MB。那么真的只是推迟了吗?
显然,如果 GC.Collect 删除了您担心的内存,则不会发生内存泄漏。如果您想真正确定,请运行测试 10 次。内存使用应该稳定。
当文件在不同的组件和框架中传输时,以单个块的形式处理如此大的文件可能会导致内存使用量成倍增加。切换到流式传输方法可能是个好主意。