线程导致GUI冻结

时间:2015-08-09 10:32:56

标签: c# .net multithreading

所以我不是最熟练的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;
            }
        }
    }
}

2 个答案:

答案 0 :(得分:5)

虽然我无法告诉您为什么您的代码确实会降低GUI的速度,但您应该在代码中执行一些操作以使其更好地全面运行。如果问题仍然存在,那么找出问题要容易得多。

  1. 创建Thread个对象非常昂贵。这就是为什么在C#中添加新类以更好地处理多线程的原因。现在您可以访问Task类或Parallel类(如下所述)。
  2. 从评论来看,你同时运行了很多线程。虽然只是运行它们不应该是一个问题,但如果您正在执行的操作是WebRequests,那么您实际上并没有多少使用它们(除非您有一个很棒的网络)。当然,请使用多个线程,但要限制它们的数量。
  3. 当您想在后台执行特定操作时,Task非常棒。但是当你想在后台为一组特定的数据重复一次操作时......为什么不使用System.Threading.Tasks.Parallel类?具体来说,Parallel.ForEach(您可以在其中指定代理列表作为参数)。此方法还允许您使用ParallelOptions设置在任何给定时刻应同时运行的线程数。
  4. 另一种编码方式是使用.NET 4.5中提供的asyncawait关键字。在这种情况下,您的GUI(按下按钮?)应该调用async方法。
  5. 使用Interlocked.IncrementInterlocked.Add等线程安全方法来增加/减少多个线程可用的计数器。另外,请考虑您可以将代理列表更改为ConcurrentDictionary<string, bool>(其中bool表示代理是否正常工作)并设置值而不必担心,因为每个线程只会访问自己的条目词典。例如,您可以使用LINQ:dictionary.Where(q => q.Value).Count()轻松地对总计进行排队,以获取工作代理的数量。当然也可以使用其他课程,具体取决于您想要解决的问题 - 也许是Queue(或ConcurrentQueue)?
  6. 你的lock不应该真正起作用......因为它似乎是偶然的,而不是你的代码中的设计(感谢Luaan的评论)。但你真的不应该这样做。请参阅lock上的MSDN documentation,以便更好地了解其工作原理。 MSDN示例中创建的Object不仅适用于show。
  7. 您还可以使用BeginGetResponseEndGetResponse方法将请求本身设置为多线程。实际上,您可以将其与Task类结合使用,以获得更清晰的代码(Task类可以将Begin / End方法对转换为单个Task对象。)
  8. 因此,快速回顾一下 - 使用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;
            }
        }
    

答案 1 :(得分:3)

尽管其他评论是正确的,您应该使用Task类,或者更好的async API,这不是您锁定的原因。

导致线程锁定的代码行是:

request.Timeout = (int)timeoutCounter.Value * 1000;

我假设timeoutCounter是WinForm的控件 - 在主GUI线程上运行
换句话说,你的线程&#39;代码试图访问一个不属于它自己的线程的控件,这个控件实际上不是“允许的”,至少不那么简单。

例如,this question显示了如何执行此操作,尽管大多数答案都有点过时了。

从一个快速谷歌(好吧,我开玩笑,我把它打包)我发现this article可以很好地解释问题。