C#频繁调用BeginInvoke时UI性能变慢
我有一个名为ProxyTesterForm的主窗体,它有一个子窗体ProxyScraperForm。当ProxyScraperForm刮取新代理时,ProxyTesterForm通过异步测试刮取的代理来处理事件,测试后将代理添加到BindingList,BindingList是DataGridView的数据源 因为我正在添加在UI线程上创建的数据绑定列表,所以我在DataGridView上调用BeginInvoke,以便在适当的线程上进行更新 在我将在下面发布的方法中,如果没有BeginInvoke调用,我可以在处理过程中在屏幕上拖动表单,它不会结巴且平滑。对于BeginInvoke呼叫,它的作用正好相反 我对如何解决这个问题有一些想法,但我想听听比我聪明的人的意见,所以我妥善解决了这个问题C#频繁调用BeginInvoke时UI性能变慢,c#,C#,我有一个名为ProxyTesterForm的主窗体,它有一个子窗体ProxyScraperForm。当ProxyScraperForm刮取新代理时,ProxyTesterForm通过异步测试刮取的代理来处理事件,测试后将代理添加到BindingList,BindingList是DataGridView的数据源 因为我正在添加在UI线程上创建的数据绑定列表,所以我在DataGridView上调用BeginInvoke,以便在适当的线程上进行更新 在我将在下面发布的方法中,如果没有BeginInvo
private void Site_ProxyScraped(object sender, Proxy proxy)
{
Task.Run(async () =>
{
proxy.IsValid = await proxy.TestValidityAsync(judges[0]);
proxiesDataGridView.BeginInvoke(new Action(() => { proxies.Add(proxy); }));
});
}
//包含所有实际数据的成员字段
列表_proxies=新列表();
//这是一个触发器:它可能是一个计时器或其他东西的ellapsed事件
OnSomeTimeorOtherTrigger()上的私有void
{
UIupdate();
}
//只是一个辅助函数
私有无效UIupdate
{
var local=_proxies.ToList();//确保静态封装
代理DataGridView.BeginInvoke(新操作(()=>
{
//向UI添加*新内容*的方法
//对本地副本执行操作
}));
}
私有无效站点\u代理已删除(对象发送方、代理)
{
Task.Run(异步()=>
{
proxy.IsValid=wait proxy.testvaliditysync(法官[0]);
//添加到列表中
_代理。添加(代理);
});
}
在Windows中,每个具有UI的线程都有一个消息队列-此队列用于为该线程的Windows发送UI消息,这些消息包括鼠标移动、鼠标上下移动等
在每个UI框架的某个地方都有一个循环,从队列中读取消息,对其进行处理,然后等待下一条消息
某些消息的优先级较低,例如,只有当线程准备好处理鼠标移动消息时,才会生成鼠标移动消息(因为鼠标经常移动)
BeginInvoke也使用这种机制,它发送一条消息,告诉循环需要运行代码
您所做的是用BeginInvoke消息淹没队列,而不是让它处理UI事件
标准解决方案是限制BeginInvoke呼叫的数量,例如,收集所有需要添加的项目,并使用一个BeginInvoke呼叫来添加所有项目
或者分批加载,如果您每秒仅对这一秒中找到的所有对象进行一次BeginInvoke调用,则可能不会影响UI响应,用户将无法分辨差异。您是否考虑过使用cancelationtoken来阻止多个同时请求?@Stefan否我没有。这不是信号灯的作用吗?我只使用cancellationtokens来取消任务。老实说,我认为您不需要
任务。请在此处运行。改为使Site\u ProxyScraped
异步,因为我假设这是一个事件处理程序。然后等待<代码>代理。TestValueTyYasyc和NeXPixKek可以保持原样。考虑层的分离:它将帮助您克服这里的许多问题。例如,看看这篇(我自己的XD)文章;关键是存储proxy.IsValid=wait proxy.testvaliditysync(判断[0])的结果在字典
或集合
中的code>。它将是超快速的,不需要与UI交互。之后,您可以考虑更新UI。也许有一个间隔500毫秒的计时器或者类似的东西。您可以从字典
或集合
更新UI,但我会将站点_ProxyScraped
转换为异步。不需要任务。在这里运行。@Stefan谢谢,我很快就会尝试一下。如果我使用此解决方案,我会将此标记为答案。@FCin从现在起,我将使用需要等待调用的事件处理程序来执行此操作。@FCin:是的,这是一个很好的建议,尽管我不是调用该函数的对象,并且如果OP能够处理调用方不异步的情况。@davidstanpher:By确保静态封装
;这是我的俚语:延迟执行和圈地。想象一下,UI正在从\u代理列表更新,而该列表在其他地方被更改,这可能是因为UI相对较慢。同时从两个不同的位置访问列表可能会导致问题:即访问可能被删除的元素。因此:制作本地副本可以克服这个问题。这就是那里发生的事情。我添加了一条评论:“在本地副本上执行操作”。感谢您的精彩解释。我觉得情况就是这样,你提出的解决方案和我想的差不多,但我想和比我更了解的人确认一下。
//member field which contains all the actual data
List<Proxy> _proxies = new List<Proxy>();
//this is some trigger: it might be an ellapsed event of a timer or something
private void OnSomeTimerOrOtherTrigger()
{
UIupdate();
}
//just a helper function
private void UIupdate
{
var local = _proxies.ToList(); //ensure static encapsulation
proxiesDataGridView.BeginInvoke(new Action(() =>
{
//someway to add *new ones* to UI
//perform actions on local copy
}));
}
private void Site_ProxyScraped(object sender, Proxy proxy)
{
Task.Run(async () =>
{
proxy.IsValid = await proxy.TestValidityAsync(judges[0]);
//add to list
_proxies.Add(proxy);
});
}