C#:BackgroundWorker 克隆资源?
这个问题
我已经为这个特定问题苦苦挣扎了两天,但没有什么想法。一点...背景:我们有一个 WinForms 应用程序,需要访问数据库,根据该数据构造相关内存中对象的列表,然后显示在 DataGridView 上。重要的一点是,我们首先填充应用程序范围的缓存(List),然后创建 DGV 所在表单本地缓存的镜像(使用 List 构造函数参数)。
由于获取数据需要花费几秒钟的时间(数据库位于 LAN 服务器上),因此我们决定使用后台工作程序,并且仅在数据加载后刷新 DGV。然而,似乎通过 BGW 进行加载会导致一些内存泄漏......或者是我的错误。当使用阻塞方法调用加载时,应用程序消耗大约 30MB 的 RAM;有了 BGW,这个值就会跳到 80MB!虽然看起来可能没那么重要,但我们的客户对此并不太高兴。
相关代码
Form
private void MyForm_Load(object sender, EventArgs e)
{
MyRepository.Instance.FinishedEvent += RefreshCache;
}
private void RefreshCache(object sender, EventArgs e)
{
dgvProducts.DataSource = new List<MyDataObj>(MyRepository.Products);
}
Repository
private static List<MyDataObj> Products { get; set; }
public event EventHandler ProductsLoaded;
public void GetProductsSync()
{
List<MyDataObj> p;
using (MyL2SDb db = new MyL2SDb(MyConfig.ConnectionString))
{
p = db.PRODUCTS
.Select(p => new MyDataObj {Id = p.ID, Description = p.DESCR})
.ToList();
}
Products = p;
// tell the form to refresh UI
if (ProductsLoaded != null)
ProductsLoaded(this, null);
}
public void GetProductsAsync()
{
using (BackgroundWorker myWorker = new BackgroundWorker())
{
myWorker.DoWork += delegate
{
List<MyDataObj> p;
using (MyL2SDb db = new MyL2SDb(MyConfig.ConnectionString))
{
p = db.PRODUCTS
.Select(p => new MyDataObj {Id = p.ID, Description = p.DESCR})
.ToList();
}
Products = p;
};
// tell the form to refresh UI when finished
myWorker.RunWorkerCompleted += GetProductsCompleted;
myWorker.RunWorkerAsync();
}
}
private void GetProductsCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (ProductsLoaded != null)
ProductsLoaded(this, null);
}
结束!
GetProductsSync 或 GetProductsAsync 在主线程上调用,上面未显示。难道 GarbageCollector 会因为两个线程而丢失吗?或者是任务管理器显示了错误的值?
对于任何回应、建议、批评,我们将不胜感激。
The problem
I've been struggling with this partiular problem for two days now and just run out of ideas. A little... background: we have a WinForms app that needs to access a database, construct a list of related in-memory objects from that data, and then display on a DataGridView. Important point is that we first populate an app-wide cache (List), and then create a mirror of the cache local to the form on which the DGV lives (using List constructor param).
Because fetching the data takes a good few seconds (DB sits on a LAN server) to load, we decided to use a BackgroundWorker, and only refresh the DGV once the data is loaded. However, it seems that doing the loading via a BGW results in some memory leak... or an error on my part. When loaded using a blocking method call, the app consumes about 30MB of RAM; with a BGW this jumps to 80MB! While it may not seem as much anyway, our clients are not too happy about it.
Relevant code
Form
private void MyForm_Load(object sender, EventArgs e)
{
MyRepository.Instance.FinishedEvent += RefreshCache;
}
private void RefreshCache(object sender, EventArgs e)
{
dgvProducts.DataSource = new List<MyDataObj>(MyRepository.Products);
}
Repository
private static List<MyDataObj> Products { get; set; }
public event EventHandler ProductsLoaded;
public void GetProductsSync()
{
List<MyDataObj> p;
using (MyL2SDb db = new MyL2SDb(MyConfig.ConnectionString))
{
p = db.PRODUCTS
.Select(p => new MyDataObj {Id = p.ID, Description = p.DESCR})
.ToList();
}
Products = p;
// tell the form to refresh UI
if (ProductsLoaded != null)
ProductsLoaded(this, null);
}
public void GetProductsAsync()
{
using (BackgroundWorker myWorker = new BackgroundWorker())
{
myWorker.DoWork += delegate
{
List<MyDataObj> p;
using (MyL2SDb db = new MyL2SDb(MyConfig.ConnectionString))
{
p = db.PRODUCTS
.Select(p => new MyDataObj {Id = p.ID, Description = p.DESCR})
.ToList();
}
Products = p;
};
// tell the form to refresh UI when finished
myWorker.RunWorkerCompleted += GetProductsCompleted;
myWorker.RunWorkerAsync();
}
}
private void GetProductsCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (ProductsLoaded != null)
ProductsLoaded(this, null);
}
End!
GetProductsSync or GetProductsAsync are called on the main thread, not shown above. Could it be that the GarbageCollector just gets lost with two threads? Or is it the task manager that shows incorrect values?
Will be greateful for any responses, suggestions, criticism.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
有趣的是 - 遵循 Henk 的建议并使用真正的分析器(.Net Memory Profiler)而不是任务管理器。
虽然内存使用数量几乎相同,但在同步和异步情况下,MyDataObj 实例的预期数量等于预期 (db),虚拟内存和堆大小也非常接近......仍然令人好奇的是正在进行中。 ntdll 对 VirtualAlloc() 的调用始终存在 1.5MB 的差异。其中大约 1MB 来自 DllUnregisterServerInternal(),在异步情况下占用 18.7MB(相对于 17.7MB)。其余大部分来自 CoUninitializeEE(),它确实在异步版本中被调用,但不被同步应用程序调用(?)。我知道,这是在挖深坑——抱歉。上面的 1.5MB 是我能找到的唯一真正的区别 - 只是我的疯狂猜测,这可能是其他事情正在发生的迹象。
真正的问题是:为什么任务管理器显示的数字截然不同?它不能很好地处理BackgroundWorkers吗?您是否遇到过如此巨大的差异(30MB 与80MB)?
Funny that - followed Henk's advice and used a real profiler (.Net Memory Profiler) rather than task manager.
While mem use numbers are almost identical, the expected number of MyDataObj instances equal to the expected (db) in both sync and async cases, Virtual Memory and Heap sizes also very close... still something curious is going on. There's always a 1.5MB difference that stems from a call to VirtualAlloc() by ntdll. About 1MB out of this comes from DllUnregisterServerInternal(), which takes up 18.7MB in the async case (vs. 17.7MB ). Most-of-the-rest comes from CoUninitializeEE() that does get called in the async version, but isn't called by the sync app (?). I know, this is digging deep in mud - apologies. The above 1.5MB is the only real difference I could find - just my wild guess that it could be a sign of something else going on.
The real question is: why does the task manager show wildly different numbers? Does it not handle BackgroundWorkers well? Have you ever come across such a massive difference (30MB vs 80MB)?
我不完全确定这是否有帮助,但在异步方法中,您可以将其更改
为
:我认为您不需要那里的额外列表变量。这可能是为什么?你正在创建一个完整的额外列表?不管怎样,这看起来也更干净一点:)
I'm not entirely sure if this will help, but in the Async method you could change this:
to this:
I don't think you need the extra list variable in there. That might be why? You were creating a whole extra list? Either way, this is also a little cleaner looking :)