6

So I'm not the most experienced with the C# programming language, however I've been making a few test applications here and there.

I've noticed that the more threads I create for an application I'm working on, the more my GUI starts to freeze. I'm not sure why this occurs, I previously thought that part of the point of multi-threading an application was to avoid the GUI from freezing up.

An explanation would be appreciated.

Also, here's the code I use to create the threads:

private void runThreads(int amount, ThreadStart address)
{
    for (int i = 0; i < amount; i++)
    {
        threadAmount += 1;
        Thread currentThread = new Thread(address);
        currentThread.Start();
    }
}

and here's what the threads run:

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;
            }
        }
    }
}
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
Potato Gun
  • 163
  • 1
  • 9
  • Quick question - which version of .NET are you using? If you're using 4 (or later) you can make use of the `Task` class. 4.5 also has the `async` and `await` keywords available. Finally, if the only reason you're using threads is to run different `WebRequest`, then said class also has a `BeginGetResponse` and `EndGetResponse` methods allowing you to run them in the background. – MBender Aug 09 '15 at 10:37
  • 4
    What is the value passed in as `amount`? How many threads are you spinning? – Yuval Itzchakov Aug 09 '15 at 10:40
  • 2
    To expand on why @YuvalItzchakov is asking for the value of `amount` - if you start a high number of threads then the GUI will suffer simply because you're overloading the system. – MBender Aug 09 '15 at 10:42
  • I'm current using .NET 4.5 for my application. The reason why I'm using multiple threads is to make the process a lot faster. So the task class is an alternative to threads? Will that help resolve the GUI freezing issues? Also, I have very little experience with C# so, to be honest, I'm not sure how I would utilize those methods. **Edit:** Currently my GUI starts lagging at about ~100 threads. However, the reason why I am confused is that I've ran different applications (not written by myself and they were coded in Java) with around ~256 threads and have had no issues with the GUI freezing. – Potato Gun Aug 09 '15 at 10:44
  • 3
    wait wait... you dont expect your cpu can handle any amount of threads? – M.kazem Akhgary Aug 09 '15 at 10:48
  • Start the application, let it run until it freezes, click the 'pause' button in visual studio. Find the Main Thread in the "Debug Location" toolbar and see what the UI/Main thread is doing. – Emond Aug 09 '15 at 10:48
  • Not at all, @M.kazemAkhgary, but as I said I've ran more threads in other applications and had to issues with the GUI freezing at all. – Potato Gun Aug 09 '15 at 10:51
  • @Erno de Weerd, I will try that and let you know how it goes. Thanks! – Potato Gun Aug 09 '15 at 10:51
  • Using a BackGroundWorker is one method to solve issue. Other solutions will also work. – jdweng Aug 09 '15 at 11:11
  • 4
    Are you trying to write an attack on http://google.co.nz? – Jodrell Aug 10 '15 at 07:37
  • ["You should not force a tag into your title. (...) The only time you should use tags in your title is when they are organic to the conversational tone of the title."](http://stackoverflow.com/help/tagging) – BCdotWEB Aug 10 '15 at 08:12
  • Absolutely not, @Jodrell. The reason why I'm using the Google website in my code is simply because of the fact that the Google website is virtually never down. – Potato Gun Aug 11 '15 at 05:02
  • I'm not sure what you mean, @BCdotWEB. I have not added any tags to my title? **Edit:** Actually, you may be correct. I guess a moderator removed it (it shows that they edited my post). Sorry about that. – Potato Gun Aug 11 '15 at 05:03

2 Answers2

6

While I cannot tell you why exactly your code is slowing the GUI down, there are a few things in your code you should do to make it all-round better. If the problem persist then, then it should be a lot easier to pinpoint the issue.

  1. Creating Thread objects is expensive. This is why new classes were added in C# to better handle multi-threading. Now you have access to the Task class or the Parallel class (described below).
  2. Judging from the comments, you're running a LOT of threads at the same time. While it shouldn't be an issue to just run them, you're not really getting much use out of them if what you're doing is firing WebRequests (unless you have an awesome network). Use multiple threads, sure, but limit their number.
  3. A Task is great when you want to do a specific operation in the background. But when you want to repeat a single operation for a specific set of data in the background... why not use the System.Threading.Tasks.Parallel class? Specifically, Parallel.ForEach (where you can specify your list of proxies as the parameter). This method also lets you set up how many threads are supposed to run concurrently at any given moment by using ParallelOptions.
  4. One more way to code would be to make use of the async and await keywords available in .NET 4.5. In this case your GUI (button press?) should invoke an async method.
  5. Use thread-safe methods like Interlocked.Increment or Interlocked.Add to increase / decrease counters available from multiple threads. Also, consider that you could change your list of proxies to a ConcurrentDictionary<string, bool> (where bool indicates if the proxy works or not) and set the values without any worry as each thread will only access its own entry in the dictionary. You can easily queue the totals at the end using LINQ: dictionary.Where(q => q.Value).Count() to get the number of working proxies, for example. Of course other classes are also available, depending on how you want to tackle the issue - perhaps a Queue (or ConcurrentQueue)?
  6. Your lock shouldn't really work... as in, it seems it's working by accident than by design in your code (thanks Luaan for the comment). But you really shouldn't do that. Consult the MSDN documentation on lock to get a better understanding of how it works. The Object created in the MSDN example isn't just for show.
  7. You can also make the requests themselves multi-threaded by using BeginGetResponse and EndGetResponse methods. You can, in fact, combine this with the Task class for a much cleaner code (the Task class can convert Begin/End method pairs into a single Task object).

So, a quick recap - use the Parallel class for multi-threading, and use concurrency classes for keeping things in place.

Here's a quick example I wrote:

    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;
        }
    }
MBender
  • 5,395
  • 1
  • 42
  • 69
  • 2
    Sadly, I'm affraid the `lock` actually *will* work - the string constant is going to be interned, so it does always point to the same instance. It's still a horrible idea, though - it's like having a synchronization object that's `public readonly static`. – Luaan Aug 10 '15 at 08:01
  • 1
    Very nice approach, first cleaning up all the obvious threading issues before trying to suss the actual problem - if it will even remain. Btw if you're already there, consider going with the naturally async HttpClient to improve parallelism... – AviD Aug 11 '15 at 08:27
3

Despite the other comments being correct in that you should use either the Task class, or better yet, the async API, that is not the reason you're locking up.

The line of code that is causing your threads to lock up is this:

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

I am assuming that timeoutCounter is a control on the WinForm - which is running on the main GUI thread.
In other words, your threads' code is trying to access a control which is not in it's own thread, which is not really "allowed", at least not so simply.

For example, this question shows how to do this, albeit most of the answers there are a bit dated.

From a quick google (okay who am I kidding, I binged it) I found this article that explains the problem rather well.

Community
  • 1
  • 1
AviD
  • 12,944
  • 7
  • 61
  • 91
  • I've tried (and I just retried) setting that to a static value (5000), however the issue still occurs. **Edit:** By the way, you were correct by assuming that ``timeoutCounter`` is a control (it's a ``NumericUpDown`` control). – Potato Gun Aug 09 '15 at 11:22
  • @PotatoGun Is there nowhere else you are referencing a control on the main GUI thread? E.g. is the `proxies` variable (string array?) defined on the Form's thread? – AviD Aug 09 '15 at 11:31
  • Wait, I might be wrong - something else to look at, could this line `Thread.SetData(Thread.GetNamedDataSlot("currentProxy"), proxies[proxyIndex]);` be the cause of the incremental freezing? MSDN states "For better performance, use fields that are marked with the ThreadStaticAttribute attribute instead." Using `.SetData()` accesses the TLS indirectly (not to mention boxing) - I'm curious if this could cause substantial overhead with many threads? – AviD Aug 09 '15 at 11:40
  • The variable is defined outside the method that the threads have access to, so that actually might be the issue. However, the reason for that is because I'm using that in multiple methods (checking and loading the proxies initially), and I'm not exactly sure if there's a workaround for that. Other variables, such as the proxyIndex variable, are stored outside of any methods, simply because of the fact that I don't want them to be re-defined with the value of 0 every time the method is entered. **Edit:** Just noticed your new message, I'll look into it in a second. Thanks! – Potato Gun Aug 09 '15 at 11:41
  • I don't think getting `timeoutCounter` could be the issue. C# throws an error when attempting to access GUI-controlled data from outside the GUI thread. – MBender Aug 10 '15 at 07:07