renchan
renchan

Reputation: 519

Freezes in multithreading app

I am trying to make a WinForms multi-threading app, which endlessly generates exceptions in two different threads.

One thread uses GenerateDllNotFoundExc() method and the other one another method, which is basically the same but simply generates another exception.

It then writes the exception message to a queue and then from the queue to text box.

However the GUI always freezes after 1 second, it writes messages to text box a bit and freezes. I tried debugging it, the code itself works however GUI is freezes.

Could someone please give me a hint as to what I'm doing wrong?

private delegate void GetQueueElem();
private event GetQueueElem getqueuelem;     

private void GenerateDllNotFoundExc()
{
    Action<String> addelem = new Action<String>(AddToQueue);

    string exdll = string.Empty;

    while (shouldgeneratemore)
    {
        try 
        {
            throw new DllNotFoundException();
        }
        catch (Exception ex) 
        {
            exdll = ex.Message;
        }

        this.Invoke(addelem, exdll);
    }
}

private void AddToQueue(string exmess)
{            
    lock (lockobject)            
        queue.Enqueue(exmess);

    getqueuelem.Invoke();
}        

private void AddToTextBox()
{           
     while (queue.Count > 0)
     {
         string s = queue.Dequeue() +"\t" + Thread.CurrentThread.Name 
             + "\t" + Thread.CurrentThread.ManagedThreadId + "\t";

         lock (lockobject)
             textBox1.Text += s;                
     }                     
}       

Upvotes: 2

Views: 2016

Answers (1)

Hans Passant
Hans Passant

Reputation: 941455

This question is educational, it shows evidence of having all three major threading bugs. Putting them roughly in order in how common they are:

  1. A threading race bug. Tripped when one thread reads a variable that is modified by another. Locking is required to avoid that from causing problems. This code uses the lock keyword but does not use it properly. The Queue class is not thread-safe, in this code both the unsafe Count property and the Dequeue() method are used without a lock. Not the actual problem here however, none of the code that uses the Queue actually runs on more than one thread. In other words, the lock isn't actually needed.

  2. Deadlock. Occurs when code acquires locks in an unpredictable order. Particularly nasty for code that runs on the UI thread of a program, it often acquires locks that are not visible, built into the .NET Framework, the operating system or various 3rd party hooks. Screen readers for example. The Invoke() method is particularly prone to deadlock and should strongly be avoided, BeginInvoke() is always preferred. You don't actually need Invoke(), you don't care about the return value. Not the actual bug in this program however, even though it looks a lot like deadlock, you can use the debugger and see that the UI thread is executing code and not stopped on a lock.

  3. A fire-hose bug. Fire-hosing occurs when the thread that produces results does so faster than the thread that processes them can consume. This kind of bug produces various kinds of misery, it can look a lot like a deadlock. Ultimately such a program will always fall over when it runs out of memory, consumed by a queue that contains too many results that have not been processed yet. Takes a while btw, .NET programs have a lot of memory available.

It is number 3 in this program. The UI thread needs to perform multiple duties and treats invoke requests with a high priority. Dispatching the invoked method, AddToQueue() in this case. It reads the invoke request from an internal queue and it tries to get the queue emptied first before doing other lower priority tasks. This goes wrong when the queue can't be emptied because a worker thread adds entries to the queue at a rate higher than the UI thread can empty it. In other words, the UI thread can never keep up, it only dispatches invoke requests and does not get around to doing anything else.

Pretty visible in Task Manager for example, you'll see your program burning 100% core. So you know it isn't actually deadlock. And very noticeable in your UI, you can bang on the Stop button but it does not have any effect. And painting no longer occurs, treated as a low priority task that's only executed when nothing more important needs to happen. It looks completely frozen, even though the UI thread is running like gangbusters.

A fire-hose bug is pretty easy to trip, it only takes a bit more than a thousand invoke requests per second. Depends on how much work the UI thread needs to do. Usually a lot, updating UI is typically pretty expensive. Nothing very subtle about setting the Text property of a TextBox, a lot of work happens under the covers. That innocent looking += operator burns a lot of cycles. Beyond the static overhead of SendMessage() to talk to the native TextBox, a lot of cycles are burned on constantly having to re-allocate the internal text buffer. Compare String vs StringBuilder. Or in other words, even if you don't trip the fire-hose bug at first, you are guaranteed you will sooner or later because the TextBox contains too much text that needs to be moved from one buffer to another. Sooner in your case.

Ultimately a fire-hose bug like this is a balancing bug. You are updating UI at a rate that is far, far higher than a human can ever observe. That is not a useful user interface. There is no practical advice for this program, it is too synthetic, intentionally slowing down the worker thread would be a workaround.

Upvotes: 13

Related Questions