线程导致GUI冻结
本文关键字:冻结 GUI 线程 | 更新日期: 2023-09-27 18:06:59
所以我不是c#编程语言最有经验的,但是我已经在这里和那里做了一些测试应用程序。
我注意到,我为我正在工作的应用程序创建的线程越多,我的GUI就开始冻结得越多。我不确定为什么会发生这种情况,我以前认为多线程应用程序的部分意义是避免GUI冻结。
请解释一下。
还有,下面是我用来创建线程的代码:
private void runThreads(int amount, ThreadStart address)
{
for (int i = 0; i < amount; i++)
{
threadAmount += 1;
Thread currentThread = new Thread(address);
currentThread.Start();
}
}
,下面是线程的运行:
private void checkProxies()
{
while (started)
{
try
{
WebRequest request = WebRequest.Create("http://google.co.nz/");
request.Timeout = (int)timeoutCounter.Value * 1000;
request.Proxy = new WebProxy(proxies[proxyIndex]);
Thread.SetData(Thread.GetNamedDataSlot("currentProxy"), proxies[proxyIndex]);
if (proxyIndex != proxies.Length)
{
proxyIndex += 1;
}
else
{
started = false;
}
request.GetResponse();
workingProxies += 1;
}
catch (WebException)
{
deadProxies += 1;
}
lock ("threadAmount")
{
if (threadAmount > proxies.Length - proxyIndex)
{
threadAmount -= 1;
break;
}
}
}
}
虽然我不能确切地告诉你为什么你的代码会减慢GUI的速度,但你应该在你的代码中做一些事情来使它全面更好。如果问题仍然存在,那么找出问题应该会容易得多。
- 创建
Thread
对象是昂贵的。这就是为什么在c#中添加了新的类来更好地处理多线程。现在可以访问Task
类或Parallel
类(如下所述)。 从评论来看,你同时运行了很多线程。虽然只是运行它们应该不是问题,但是如果您正在做的事情是触发 - 当你想在后台做一个特定的操作时,
Task
是很好的。但是,当您希望在后台对特定数据集重复单个操作时……为什么不使用System.Threading.Tasks.Parallel
类?具体来说,是Parallel.ForEach
(您可以在其中指定代理列表作为参数)。该方法还允许您使用ParallelOptions
设置在任何给定时刻并发运行的线程数。另一种编码方式是利用。net 4.5中可用的async
和await
关键字。在这种情况下,你的GUI(按下按钮?)应该调用一个async
方法。 - 使用线程安全的方法,如
Interlocked.Increment
或Interlocked.Add
来增加/减少多个线程可用的计数器。另外,考虑一下您可以将代理列表更改为ConcurrentDictionary<string, bool>
(其中bool
指示代理是否工作)并设置值,而无需担心,因为每个线程只访问字典中自己的条目。例如,您可以使用LINQ:dictionary.Where(q => q.Value).Count()
轻松地在最后将总数排队,以获得工作代理的数量。当然,其他类也可用,这取决于你想如何解决这个问题-也许是Queue
(或ConcurrentQueue
)? - 你的
lock
不应该真的工作…在,它似乎是偶然的工作,而不是设计在你的代码(感谢Luaan的评论)。但你真的不应该这么做。查阅MSDN文档lock
,以更好地理解它是如何工作的。在MSDN示例中创建的Object
不仅仅是为了展示。 - 您也可以通过使用
BeginGetResponse
和EndGetResponse
方法使请求本身多线程。事实上,您可以将其与Task
类结合使用,以获得更简洁的代码(Task
类可以将Begin/End方法对转换为单个Task
对象)。
WebRequests
(除非您有一个很棒的网络),那么您就不会真正使用它们。可以使用多个线程,但是要限制它们的数量。Parallel
类来实现多线程,并使用并发类来保持事物的适当性。
下面是我写的一个简短的例子:
private ConcurrentDictionary<string, bool?> values = new ConcurrentDictionary<string, bool?>();
private async void Button_Click(object sender, RoutedEventArgs e)
{
var result = await CheckProxies();
label.Content = result.ToString();
}
async Task<int> CheckProxies()
{
//I don't actually HAVE a list of proxies, so I make up some data
for (int i = 0; i < 1000; i++)
values[Guid.NewGuid().ToString()] = null;
await Task.Factory.StartNew(() => Parallel.ForEach(values, new ParallelOptions() { MaxDegreeOfParallelism = 10 }, this.PeformOperation));
//note that with maxDegreeOfParallelism set to a high value (like 1000)
//then I'll get a TON of failed requests simply because I'm overloading the network
//either that or google thinks I'm DDOSing them... >_<
return values.Where(v => v.Value == true).Count();
}
void PeformOperation(KeyValuePair<string, bool?> kvp)
{
try
{
WebRequest request = WebRequest.Create("http://google.co.nz/");
request.Timeout = 100;
//I'm not actually setting up the proxy from kvp,
//because it's populated with bogus data
request.GetResponse();
values[kvp.Key] = true;
}
catch (WebException)
{
values[kvp.Key] = false;
}
}
尽管其他评论是正确的,因为您应该使用Task
类,或者更好的是,async
API,这不是您锁定的原因。
导致线程锁定的代码行是:
request.Timeout = (int)timeoutCounter.Value * 1000;
我假设timeoutCounter
是WinForm - 上的控件,它运行在主GUI线程上。换句话说,你的线程的代码试图访问一个控件,这不是在它自己的线程,这不是真正的"允许",至少不是那么简单。
例如,这个问题展示了如何做到这一点,尽管大多数答案都有点过时。
从一个快速谷歌(好吧,我在开玩笑,我狂欢)我找到了这篇文章,解释了这个问题相当好。