12

I have a weird issue:

In my C# app, I am creating another thread, like so:

Thread printThread = new Thread(printWorker);

printThread.Name = "Logger MainThread";
printThread.IsBackground = true;
printThread.Start();

When my main thread finishes, this new thread just keeps on working, although it's marked as Background.

What could be the causes for this? This object is holding a Mutex object, not sure this may be the reason...

Any ideas anyone?

Here's the code from the printWorker method:

while (loggerIsActive)
{
    LogMessage log = LoggerQueue.Dequeue();

    if (log.message != null)
    {
        syncLogObj.WaitOne();
        lock (writerobj)
        {
            StreamWriter sw;

            if (!File.Exists(fName))
            {
                sw = File.CreateText(fName);
            }
            else
            {
                sw = new StreamWriter(fName, true);
            }

            using (sw)
            {
                if (log.message != "")
                {
                    if (log.message.EndsWith("\r\n"))
                    {
                        log.message =
                            log.message.Substring(0, log.message.Length - 2);
                    }

                    sw.WriteLine(string.Format("[{0}][{3}][{1}] | {2}",
                                               log.msgTime,
                                               log.level.ToString(),
                                               log.message,
                                               log.sender.ToString()));
                }

                sw.Flush();
                sw.Close();
            }
        }

        syncLogObj.ReleaseMutex();
    }

    Thread.Sleep(5);
}
7
  • 2
    Based on the fact that the thread is not being killed by the OS, the only thing that I could see that would keep that thread doing anything is the while loop. Starting with the obvious, is loggerIsActive being set to false? Commented May 28, 2011 at 21:51
  • Can you post your main() method? Commented May 28, 2011 at 22:27
  • The while loop is being run on another thread, which is marked as Background. Why is it sticking around after the app that loads it is closed? Commented May 29, 2011 at 5:13
  • 1
    Are you sure that your main thread is finished?
    – Maxim
    Commented May 31, 2011 at 22:44
  • I have tested again, but the issue is failing to reproduce. I will keep an eye for it though. The main thread has exited for sure when that issue occured. Commented Jun 1, 2011 at 13:37

6 Answers 6

12
+25

Try this:

Start the app through VS and exit normally. The VS should stay in Debug mode as you described. Click on Pause button (Break all) and then go to Debug->Windows->Threads. Do you see your "Logger MainThread" in the list?

  • If so, double-click it, it should lead you to the code line that the thread is currently executing. Step-debug from there and see why is it not terminating.
  • If you don't see it try looking at other threads that have not terminated and try to find the problem.

Otherwise, with those kind of problems it's always useful to monitor the program state via System.Diagnostics.Debug.Print statements (you can see them printing in the VS output window).

8
  • @boris this is exactly what i did already. Close main app, VS stays in debug mode. CTRL-ALT-BREAK, throws me into the line Thread.Sleep(5) in the code i posted above. This thread should've been aborted when the application is closing. Commented May 29, 2011 at 5:15
  • @liortal: Make the local var printThread into a private field and then when you pause at the Thread.Sleep(5) check in Immediate window _printThread.IsBackground; What does it say? Do you get AbandonedMutexException when you close the main app? And where does the step-debugging (F10) takes you after the Sleep(5) line?
    – Boris B.
    Commented May 29, 2011 at 14:14
  • @boris i will make it into a private field. Are there scenarios where a thread defined as Background will not be launched as a background thread? Also i am not getting this exception, Which thread/AppDomain shuold throw this type of exception ? Commented May 29, 2011 at 17:58
  • 10
    You should have mentioned that small detail that the logger thread is running in another AppDomain :). Another AppDomain is another isolated exec. environment, and I wouldn't be surprised that closing the app doesn't kill threads from another appdomain. In fact I would be surprised if it was so, because then it wouldn't be an isolated excecution environment. And now I see why you needed a fully-fledged Mutex object instead of a simple lock...
    – Boris B.
    Commented May 29, 2011 at 22:02
  • 2
    That is just a suggestion, i thought of this explanation due to the lack of any other explanations. I didn't write this code, and i am not aware on which contexts are creating it. Now that we raised this option, it fits the bill -- if some code in another AppDomain was creating this object & spawning the new thread, this thread will not exit when the main AppDomain is unloaded. Will verify this tomorrow @ the office. If this is the case, i need to come up with a solution to destroy this app domain when the app closes. Commented May 29, 2011 at 22:33
7

kill it.

Not pretty. But this isn't TV. Read on:

1) Not sure you use are using it but it appears you should be locking loggerqueue before you queue(main pgm) or dequeue(thread).
2) No need to lock writerobj with just this setting. But really you should so you can safely kill the thread not during a write:

  • main thread:
    • do everything
    • before close: -lock writerobj -printthread.abort
  • worker thread:
    • add try catch to handle threadabort exception and just quit

If you're properly doing this, you shouldn't have to use Waits and mutexes. If you are using wait properly anyway you won't need the sleep.

General advice for this application: why not log on main thread? if your logging is that busy, log results will be pretty useless.

But there are rare cases where that might be wrong. Entonces......

General advice to have threads play nice for this problem:

  • Main program
    • encapsulate logging (notably, quit flag, queue, and worker thread ref) in an object
    • 'global snobs?' Logging is a rare excuse to use singleton patter.
    • start worker thread in logger object via method
    • main thread always calls a single method on logger object to log error
    • That method locks the queue and adds to it.
    • Use Monitor/Pulse/Wait, no sleep; full examples abound; it is worth learning
      • because only this thread is hitting the file anyway, unless you have multiple processes, you don't need waitone/releasemutex.
    • That logging method monitor.pulses an object
    • That frees the worker thread's monitor.wait (which is what idles the CPU instead of sleep)
    • lock the queue, only inside the lock dequeue the object to local ref; nothing else.
    • Do your normal logging code and 'exit check' loop. Add
      • Your logic code could leave message unwritten if queue is full on quit:
      • change to exit check so you can do it without an extra lock of queue:
        • move declaration of queued object refernce above while; set it to nothing
        • change logic in while to 'loggerisactive or log != null'
    • when your main thread finishes, in your exit code:
      • set the quit flag
      • pulse the object you're using to wait incase it's not processing the queue
      • Thread will fall thru.
2
  • Thanks, i believe i need to have 2-3 thorough readings of your answer to grasp it to the fullest. Commented Jun 3, 2011 at 9:44
  • His code does not busy-wait (spins with Thread.Sleep). That Mutex called syncObj is not used for single-access but for waiting on event. The main thread initially owns the mutex. When it has something to log it then queues the record, calls Mutex.Release and then Mutex.WaitOne. That Release causes the logger thread to advance until it loops-back to his WaitOne, which causes the main thread to advance, which has been waiting since. That Sleep was probably a debugging leftover or something, it does not affect the code.
    – Boris B.
    Commented Jun 3, 2011 at 23:49
4

You have a lot of stuff going on that you're obviously not showing...

Exmaple: you have syncLogObj.WaitOne();, but we don't see where syncLogObj is being declared, or used elsewhere in your program.

Plus, you don't need it... get rid of the syncLogObj thing altogether (including the "ReleaseMutex" garbage)... you already have a lock (blah) { }, and that's all you need (from what code you have displayed).

It's likely that the main thread is NOT ending, likely because of this or some other object that is keeping it open.

So, simple instructions

  1. Get rid of syncLogObj (because you already have the "lock")
  2. Make sure you set loggerIsActive = false somewhere.

Edit: Even more details!

From what I see - you don't need the lock (writerobj) at all, because (I'm quite sure), you only seem to have one thread that is writing to the log.

The "lock" is only there if you have two or more threads that running that code (basically).

2
  • I haven't looked closely at the whole system that's built around this Logger code, nor have I myself written that code. I know it is pretty horrible and features a lot of redundant locking. For the time being, the problem is not reproducing at all anymore. That is of course no excuse for not fixing the Logger to be more efficient. But that also doesn't help us figuring out what was the exact original issue. Commented Jun 3, 2011 at 9:46
  • The mutex (syncLogObj) in the code is not for locking, it's for waiting on event, see my comment on FastAl's answer for explanations. However that lock(writerobj) is redundant because the mutex already took care of single-access.
    – Boris B.
    Commented Jun 4, 2011 at 0:16
3

If printworker does not finish before your main thread is done, then main will die and your printworker thread will be killed by the OS. If you want main to wait for the thread you created, then you should call printThread.Join() in main. That will get main to wait on your thread.

When main finishes your program dies and your printThread will be destroyed by the OS, It will not keep running.

From here

Background threads are identical to foreground threads with one exception: a background thread does not keep the managed execution environment running. Once all foreground threads have been stopped in a managed process (where the .exe file is a managed assembly), the system stops all background threads and shuts down.

6
  • 1
    that's the problem, it is still running. Commented May 24, 2011 at 11:24
  • so per that info, it cannot possibly still be running after main exits Commented May 24, 2011 at 11:32
  • it is a fact -- running the program from VS2008, main thread quits, visual studio is not fully exiting yet. CTRL-ALT-BREAK, i see that this thread is still running whatever job it's doing, even if it's a background thread. Same thing when running the .EXE not under VS2008. Commented May 24, 2011 at 11:34
  • where did you get this information, when " Same thing when running the .EXE not under VS2008" ? How do you identify this particular thread? Commented May 24, 2011 at 11:37
  • 1
    This response is clearly not what the poster is looking for. He already knows this, his question is why is this not happening.
    – theMayer
    Commented Jun 11, 2013 at 12:25
2

Tony the Tiger has the right idea but additional code needs to be added to kill the thread before the application closes.

printThread.Join(1000);
if(printThread!=null && printThread.IsAlive)
    printThread.Abort();
1
Thread.Abort();
Thread.Dispose();

That should do it if I'm not mistaken.

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.