Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C#
  4. Waiting for thread to exit?

Waiting for thread to exit?

Scheduled Pinned Locked Moved C#
data-structuresdebuggingquestion
28 Posts 5 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Dave Kreskowiak

    Very interesting. Well, I verified it. That's the most screwed up thing ever. But, I hate to tell you this, but the one running under the debugger is lying to you. It's the one that's screwed up, not the one executing out of the debugger. For some reason, the debugger is keeping the Task alive when it shouldn't be. Tasks are running out of the thread pool, which are all background threads. When your main thread exited, it took the Task with it, killing the threadCurrent.Join and never allowing it to get to b = true. That's why your worker thread stays alive. I'd convert the Task to a foreground Thread instead. [EDIT] Oh, I almost forgot! You might want to post this on Connect[^] for a possible Visual Studio bug.

    A guide to posting questions on CodeProject

    Click this: Asking questions is a skill. Seriously, do it.
    Dave Kreskowiak

    L Offline
    L Offline
    Lost User
    wrote on last edited by
    #11

    Tried the code this morning, first thing I did was change it to a foreground-thread. Strangely enough that made no difference at all. I can imagine that the main-thread isn't terminated, but stopped by the debugger. That would explain why the background-task that should have terminated didn't.

    Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]

    S 1 Reply Last reply
    0
    • P PIEBALDconsult

      I think you have it backward. The main thread should wait on the background/worker thread, not the other way around.

      D Offline
      D Offline
      Dave Kreskowiak
      wrote on last edited by
      #12

      It does seem like he's relying on a quirkish feature, doesn't it?

      A guide to posting questions on CodeProject

      Click this: Asking questions is a skill. Seriously, do it.
      Dave Kreskowiak

      S 1 Reply Last reply
      0
      • D Dave Kreskowiak

        Very interesting. Well, I verified it. That's the most screwed up thing ever. But, I hate to tell you this, but the one running under the debugger is lying to you. It's the one that's screwed up, not the one executing out of the debugger. For some reason, the debugger is keeping the Task alive when it shouldn't be. Tasks are running out of the thread pool, which are all background threads. When your main thread exited, it took the Task with it, killing the threadCurrent.Join and never allowing it to get to b = true. That's why your worker thread stays alive. I'd convert the Task to a foreground Thread instead. [EDIT] Oh, I almost forgot! You might want to post this on Connect[^] for a possible Visual Studio bug.

        A guide to posting questions on CodeProject

        Click this: Asking questions is a skill. Seriously, do it.
        Dave Kreskowiak

        S Offline
        S Offline
        SledgeHammer01
        wrote on last edited by
        #13

        Thank you for your attempt at an explanation of this bizarre behavior, but unfortunately, its not quite accurate :). I will show you why with two further examples :). First off, we can all agree that Worker is a foreground thread and that it keeps the process alive regardless of what the main threads state is, right? When the PROCESS exits, it will abort any BACKGROUND threads abruptly. We can all agree on that too. Since Worker is blocking the process from exiting, there is no reason for any background threads to get killed since the process is still alive. EXAMPLE #1:

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading;
        using System.Threading.Tasks;

        namespace ConsoleApplication2
        {
        class Program
        {
        static bool b = false;

        	static void Main(string\[\] args)
        	{
        		Thread threadCurrent = Thread.CurrentThread;
        
        		Task.Run(() =>
        		{
        			while (threadCurrent.IsAlive)
        			{
        				System.Diagnostics.Debug.WriteLine("MAIN THREAD IS ALIVE");
        				Console.WriteLine("MAIN THREAD IS ALIVE");
        				Thread.Sleep(250);
        			}
        
        			System.Diagnostics.Debug.WriteLine("MAIN THREAD IS DEAD");
        			Console.WriteLine("MAIN THREAD IS DEAD");
        
        			b = true;
        		});
        
        		Thread \_threadWorker = new Thread(new ThreadStart(Worker));
        
        		\_threadWorker.SetApartmentState(ApartmentState.STA);
        		\_threadWorker.Start();
        
        		Thread.Sleep(5000);
        	}
        
        	private static void Worker()
        	{
        		while (true)
        		{
        			Thread.Sleep(100);
        
        			if (b)
        			{
        				Console.WriteLine("2) MAIN THREAD IS DEAD");
        				break;
        			}
        		}
        	}
        }
        

        }

        With this example, we can clearly see that the Task is not getting killed (or at least it appears that way) because everything works as expected. You get "MAIN THREAD IS ALIVE" messages for 5 seconds and then you get a "MAIN THREAD IS DEAD" message AND the "2) MAIN THREAD IS DEAD" message from the worker thread. Everything worked as expected and all messages were printed as expected and everything exited cleanly. A race condition perhaps? Perhaps... Perhaps not :). I've never gotten any of the messages not to print with this implementation, but lets bump up the 250ms sleep to 3000ms and try that... Nope... still works as expected, everything behaves correctly :). EXAMPLE #2: This is my favorite one :)... I'm just replacing the Task code from the code above with this Task implementation:

        Task.Run(() =>
        {
        //while (threadCurrent.IsAlive)
        //{

        1 Reply Last reply
        0
        • L Lost User

          Tried the code this morning, first thing I did was change it to a foreground-thread. Strangely enough that made no difference at all. I can imagine that the main-thread isn't terminated, but stopped by the debugger. That would explain why the background-task that should have terminated didn't.

          Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]

          S Offline
          S Offline
          SledgeHammer01
          wrote on last edited by
          #14

          Oh, I was going to try make that a foreground thread... so thanks for saving me the trouble :). Check out my response to Dave that further proves his explanation is not correctly. The main jist of the two further examples I posted was to prove that the Task is most certainly not getting killed and I wouldn't expect it to since the process is still alive.

          1 Reply Last reply
          0
          • P PIEBALDconsult

            I think you have it backward. The main thread should wait on the background/worker thread, not the other way around.

            S Offline
            S Offline
            SledgeHammer01
            wrote on last edited by
            #15

            Do you also need to tell your watch dog to bark and attack the guy breaking into your house in the middle of the night? :). The whole point of the watch dog thread is to encapsulate expected behavior. Expected behavior being that as a developer, if I send 100 items into the queue (an API), I would expect all 100 to get processed without me having to implement a "wait for the queue to be done" event of some sort. I want that to be encapsulated into the API magically. What I'm getting at is, the Worker thread is automatically created when the application starts (as part of my API), I thought it'd be kind of cheezy to have to make the developer remember to call theAPI.Shutdown()... That'd be like Microsoft making you add an Exit() call at the end of your main() in a console app.

            P 1 Reply Last reply
            0
            • D Dave Kreskowiak

              It does seem like he's relying on a quirkish feature, doesn't it?

              A guide to posting questions on CodeProject

              Click this: Asking questions is a skill. Seriously, do it.
              Dave Kreskowiak

              S Offline
              S Offline
              SledgeHammer01
              wrote on last edited by
              #16

              I think what I'm trying to do is pretty reasonable. Worker is actually a service (think service in the sense of Dependency Injection or Service Locator pattern) that services a queue. I don't know when (or if) messages will get thrown into the queue, so the queue needs to be monitored for the life of the process (its a blocking queue). If Worker was a background task, I would either need the app to implement some kind of "wait for all the messages to be processed" behavior or have messages lost. In fact, if I make the Worker a background thread and have the console app send a 1000 messages into the queue, the process would exit after only 1 or 2 of the messages were serviced. NOT DESIRABLE. So while I stripped all that out of my examples, the real intention is for the watch dog task to wait for the main thread to shutdown AND all the remaining messages in the queue to be processed and then set "b = true" so the worker thread can shut down (which makes the process shutdown cleanly). Not too unreasonable, huh? :) Like I said in another response (I think to Eddy)... when I make reusable APIs, I like to encapsulate all the behavior / requirements / code I can into the class so the user of my API can use it in a simple way.

              D 1 Reply Last reply
              0
              • S SledgeHammer01

                Do you also need to tell your watch dog to bark and attack the guy breaking into your house in the middle of the night? :). The whole point of the watch dog thread is to encapsulate expected behavior. Expected behavior being that as a developer, if I send 100 items into the queue (an API), I would expect all 100 to get processed without me having to implement a "wait for the queue to be done" event of some sort. I want that to be encapsulated into the API magically. What I'm getting at is, the Worker thread is automatically created when the application starts (as part of my API), I thought it'd be kind of cheezy to have to make the developer remember to call theAPI.Shutdown()... That'd be like Microsoft making you add an Exit() call at the end of your main() in a console app.

                P Offline
                P Offline
                PIEBALDconsult
                wrote on last edited by
                #17

                Have a WatchDog object that implements IDisposable. Done.

                S 2 Replies Last reply
                0
                • P PIEBALDconsult

                  Have a WatchDog object that implements IDisposable. Done.

                  S Offline
                  S Offline
                  SledgeHammer01
                  wrote on last edited by
                  #18

                  Who creates the watch dog object? The developer? Or the API?

                  1 Reply Last reply
                  0
                  • P PIEBALDconsult

                    Have a WatchDog object that implements IDisposable. Done.

                    S Offline
                    S Offline
                    SledgeHammer01
                    wrote on last edited by
                    #19

                    How would that work anyways? The user of the API would have to manually create the watch dog object in some class that lives in the main thread, correct? (if I'm understanding your idea). That's even uglier then calling theAPI.Shutdown().

                    P 1 Reply Last reply
                    0
                    • S SledgeHammer01

                      How would that work anyways? The user of the API would have to manually create the watch dog object in some class that lives in the main thread, correct? (if I'm understanding your idea). That's even uglier then calling theAPI.Shutdown().

                      P Offline
                      P Offline
                      PIEBALDconsult
                      wrote on last edited by
                      #20

                      Then shouldn't the API be IDisposable?

                      S 1 Reply Last reply
                      0
                      • P PIEBALDconsult

                        Then shouldn't the API be IDisposable?

                        S Offline
                        S Offline
                        SledgeHammer01
                        wrote on last edited by
                        #21

                        Mind you, I'm not trying to argue with you :) (just in case you are starting to get annoyed lol)... The API is implemented as a service (in the DI / SL sense of the word service, not as a Windows Service), so the instance of my API ends up being a static singleton instance inside of the DI container. It wouldn't get disposed unless the user manually called Dispose on the container. Calling Dispose() on the container is only a requirement if the container contains objects with unmanaged resources that need to be freed up. That is *very* rare in my world as 99.99999% of our stuff is strictly managed. I know I'm sounding picky by not wanting to add any additional burden on developers that I don't need to, but you'd have to understand the type of people I work with to understand why... :). Like the type of people who milk a literal 5 minute task for 6 months to a year type of people. Like the type of people who rather then fix memory leaks in applications simply write scripts to reboot the machine daily type of people :).

                        P 1 Reply Last reply
                        0
                        • S SledgeHammer01

                          Mind you, I'm not trying to argue with you :) (just in case you are starting to get annoyed lol)... The API is implemented as a service (in the DI / SL sense of the word service, not as a Windows Service), so the instance of my API ends up being a static singleton instance inside of the DI container. It wouldn't get disposed unless the user manually called Dispose on the container. Calling Dispose() on the container is only a requirement if the container contains objects with unmanaged resources that need to be freed up. That is *very* rare in my world as 99.99999% of our stuff is strictly managed. I know I'm sounding picky by not wanting to add any additional burden on developers that I don't need to, but you'd have to understand the type of people I work with to understand why... :). Like the type of people who milk a literal 5 minute task for 6 months to a year type of people. Like the type of people who rather then fix memory leaks in applications simply write scripts to reboot the machine daily type of people :).

                          P Offline
                          P Offline
                          PIEBALDconsult
                          wrote on last edited by
                          #22

                          Isn't a thread an unmanaged resource?

                          S 1 Reply Last reply
                          0
                          • P PIEBALDconsult

                            Isn't a thread an unmanaged resource?

                            S Offline
                            S Offline
                            SledgeHammer01
                            wrote on last edited by
                            #23

                            You mean as in System.Threading.Thread and Tasks? Well, personally, I'm conscientious about cleaning up everything I create, but threads and tasks clean themselves up when they exit. I was more referring to stuff that would cause a memory leak or OS instability if you didn't free it up like say COM objects or unmanaged bitmaps, that sort of thing. But to your original point, my worker thread is specifically a foreground thread because I WANT it to "hang" / keep the process alive until its done. I guess what you're saying is that technically this would qualify for my earlier requirement of only forcing the guy to call Dispose() on the container if something needs to be cleaned up, right? :) Yeah, I guess... except in my head, I'm differentiating between stock services that are part of my API and magically registered automatically vs. any services the developer registers :). Maybe I shouldn't make that differentiation, but in my company, its perfectly acceptable to blame your co-worker for their screw up in using your framework, better then you getting crap for your "buggy framework" :) So that's really why I'm being so picky in trying to handle as much as I can in my API. So any bugs I can just throw back at the developer that he didn't use my framework right. That's how my company works unfortunately.

                            1 Reply Last reply
                            0
                            • S SledgeHammer01

                              I think what I'm trying to do is pretty reasonable. Worker is actually a service (think service in the sense of Dependency Injection or Service Locator pattern) that services a queue. I don't know when (or if) messages will get thrown into the queue, so the queue needs to be monitored for the life of the process (its a blocking queue). If Worker was a background task, I would either need the app to implement some kind of "wait for all the messages to be processed" behavior or have messages lost. In fact, if I make the Worker a background thread and have the console app send a 1000 messages into the queue, the process would exit after only 1 or 2 of the messages were serviced. NOT DESIRABLE. So while I stripped all that out of my examples, the real intention is for the watch dog task to wait for the main thread to shutdown AND all the remaining messages in the queue to be processed and then set "b = true" so the worker thread can shut down (which makes the process shutdown cleanly). Not too unreasonable, huh? :) Like I said in another response (I think to Eddy)... when I make reusable APIs, I like to encapsulate all the behavior / requirements / code I can into the class so the user of my API can use it in a simple way.

                              D Offline
                              D Offline
                              Dave Kreskowiak
                              wrote on last edited by
                              #24

                              I didn't say that wasn't unreasonable. What I said was relying on a quirk of background/forground threads. What you're scribing, I would probably use either events or other callbacks to signal completions. I wouldn't have the main thread terminating and then waiting for a "background" thread to terminate itself. I'd keep the main thread around waiting for the background to finish so the main thread can receive notification that it is done and can keep other things alive, such logging API's or other things.

                              A guide to posting questions on CodeProject

                              Click this: Asking questions is a skill. Seriously, do it.
                              Dave Kreskowiak

                              S 1 Reply Last reply
                              0
                              • D Dave Kreskowiak

                                I didn't say that wasn't unreasonable. What I said was relying on a quirk of background/forground threads. What you're scribing, I would probably use either events or other callbacks to signal completions. I wouldn't have the main thread terminating and then waiting for a "background" thread to terminate itself. I'd keep the main thread around waiting for the background to finish so the main thread can receive notification that it is done and can keep other things alive, such logging API's or other things.

                                A guide to posting questions on CodeProject

                                Click this: Asking questions is a skill. Seriously, do it.
                                Dave Kreskowiak

                                S Offline
                                S Offline
                                SledgeHammer01
                                wrote on last edited by
                                #25

                                It's actually a pretty similar scenario to a logging API, an asynchronous one. Think about Log4Net or NLog or whatever. They have an email listener for example. That's going to be "slow". But regardless, lets say your console app writes 1000 log entries to the log, the Log() method is going to return right away because its async. So the main thread is going to be done and exit after like a second, but its going to take the logger a minute or two or whatever to send all those emails. Yes, I could have a design like: theLogger.Flush(); / theLogger.Shutdown(); and have that method block the main thread until its done. or I can do what you said: theLogger.OnComplete += blah... and have the blah method block the main process. But I see a few issues with both of those methods IMHO 1) Just like my queue implementation, a logger queue isn't going to have log entries to write all the time... how is the logger to know when to send the OnComplete() notification? Lets say you send it every time the queue goes empty... ok, well, now you require even more work on the developers part as they have to know if this OnComplete() event is the one they really care about and then they have to couple that with some mechanism to block the thread. Then as the developer of the API, you're going to get blamed cuz the developer didn't implement it right! 2) the .Flush() / .Shutdown() / etc. is cleaner and much less error prone, but this requires the developer to remember to call .Flush() / .Complete() and of course, you'll get blamed for a "buggy framework" when they forget because "your framework should handle this automatically -- hanging is a 'bug'". 3) finally, and this kind of a nitpick on my part... I see a logger (and my queue) as a fire & forget thing... I don't care about getting logging notifications back, I don't care about whether the logging queue is empty or not, I don't want to concern myself with any of that. I simply want to call Log("whatever") and move on with my life. Same thing with my queue. This is what I'm trying to implement here... a mechanism that'll shut down the queue automatically.

                                1 Reply Last reply
                                0
                                • S SledgeHammer01

                                  If you say so :)... but here you go... here's a simple console app that demonstrates it. Run this IN THE DEBUGGER (F5) and the process will exit after 5 seconds. Now run it again with CTRL-F5 (NO DEBUGGER ATTACHED) and you'll see the process never exits.

                                  using System;
                                  using System.Collections.Generic;
                                  using System.Linq;
                                  using System.Text;
                                  using System.Threading;
                                  using System.Threading.Tasks;

                                  namespace ConsoleApplication2
                                  {
                                  class Program
                                  {
                                  static bool b = false;

                                  	static void Main(string\[\] args)
                                  	{
                                  		Thread threadCurrent = Thread.CurrentThread;
                                  
                                  		Task.Run(() =>
                                  		{
                                  			threadCurrent.Join();
                                  			b = true;
                                  		});
                                  
                                  		Thread \_threadWorker = new Thread(new ThreadStart(Worker));
                                  		\_threadWorker.SetApartmentState(ApartmentState.STA);
                                  		\_threadWorker.Start();
                                  
                                  		Thread.Sleep(5000);
                                  	}
                                  
                                  	private static void Worker()
                                  	{
                                  		while (true)
                                  		{
                                  			Thread.Sleep(100);
                                  
                                  			if (b)
                                  				break;
                                  		}
                                  	}
                                  }
                                  

                                  }

                                  L Offline
                                  L Offline
                                  Lost User
                                  wrote on last edited by
                                  #26

                                  Task.Run create a new thread on the threadpool, a backgroundworker that runs in its own thread. That will block until it can join with the main-thread(*!), and die as soon as the main-thread dies (after five seconds), and Joins on the mainthread. It is that task that I moved to a foreground thread, not the worker. *! that means you wait until the proces exits and this task is stopped. B should never become true.

                                  SledgeHammer01 wrote:

                                  Background threads only get killed when there are no more foreground threads. There is one in this case: the Worker.

                                  Where does it say that in the documentation? AFAIK, the behaviour of the background thread is not guaranteed when the proces (the main thread) is terminated. http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground(v=vs.100).aspx[^]

                                  Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]

                                  S 1 Reply Last reply
                                  0
                                  • L Lost User

                                    Task.Run create a new thread on the threadpool, a backgroundworker that runs in its own thread. That will block until it can join with the main-thread(*!), and die as soon as the main-thread dies (after five seconds), and Joins on the mainthread. It is that task that I moved to a foreground thread, not the worker. *! that means you wait until the proces exits and this task is stopped. B should never become true.

                                    SledgeHammer01 wrote:

                                    Background threads only get killed when there are no more foreground threads. There is one in this case: the Worker.

                                    Where does it say that in the documentation? AFAIK, the behaviour of the background thread is not guaranteed when the proces (the main thread) is terminated. http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground(v=vs.100).aspx[^]

                                    Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]

                                    S Offline
                                    S Offline
                                    SledgeHammer01
                                    wrote on last edited by
                                    #27

                                    It says it right in the documentation you linked:

                                    Quote:

                                    Background threads are identical to foreground threads, except that background threads do not prevent a process from terminating

                                    In plain English, background threads do NOT block the process from terminating while foreground threads DO.

                                    Quote:

                                    Once all foreground threads belonging to a process have terminated, the common language runtime ends the process. Any remaining background threads are stopped and do not complete.

                                    Says right here that as soon as all foreground threads are finished, the process exits. Background threads are terminated on the spot. I don't know if you read every post in this thread, but there was one where I posted additional proof of it being broken and that the task is staying around as it should: http://www.codeproject.com/Messages/4940135/Re-Waiting-for-thread-to-exit.aspx[^]

                                    L 1 Reply Last reply
                                    0
                                    • S SledgeHammer01

                                      It says it right in the documentation you linked:

                                      Quote:

                                      Background threads are identical to foreground threads, except that background threads do not prevent a process from terminating

                                      In plain English, background threads do NOT block the process from terminating while foreground threads DO.

                                      Quote:

                                      Once all foreground threads belonging to a process have terminated, the common language runtime ends the process. Any remaining background threads are stopped and do not complete.

                                      Says right here that as soon as all foreground threads are finished, the process exits. Background threads are terminated on the spot. I don't know if you read every post in this thread, but there was one where I posted additional proof of it being broken and that the task is staying around as it should: http://www.codeproject.com/Messages/4940135/Re-Waiting-for-thread-to-exit.aspx[^]

                                      L Offline
                                      L Offline
                                      Lost User
                                      wrote on last edited by
                                      #28

                                      SledgeHammer01 wrote:

                                      I don't know if you read every post in this thread

                                      Yup.

                                      SledgeHammer01 wrote:

                                      Says right here that as soon as all foreground threads are finished, the process exits. Background threads are terminated on the spot.

                                      The proces is considered terminated if all foreground threads are terminated, true; I do not see a specific guarantee that the background-threads keep running if you kill the mainthread. I can only confirm that the behaviour is not directly what I expected, as the documentation still does not explain why the first codedump behaves as it does :)

                                      Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]

                                      1 Reply Last reply
                                      0
                                      Reply
                                      • Reply as topic
                                      Log in to reply
                                      • Oldest to Newest
                                      • Newest to Oldest
                                      • Most Votes


                                      • Login

                                      • Don't have an account? Register

                                      • Login or register to search.
                                      • First post
                                        Last post
                                      0
                                      • Categories
                                      • Recent
                                      • Tags
                                      • Popular
                                      • World
                                      • Users
                                      • Groups