线程导致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冻结

虽然我不能确切地告诉你为什么你的代码会减慢GUI的速度,但你应该在你的代码中做一些事情来使它全面更好。如果问题仍然存在,那么找出问题应该会容易得多。

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

例如,这个问题展示了如何做到这一点,尽管大多数答案都有点过时。

从一个快速谷歌(好吧,我在开玩笑,我狂欢)我找到了这篇文章,解释了这个问题相当好。