0

I'm writing an application that uses a separate thread for logging. I'd like to stop the separate thread when the main thread stops. However I'm unable to figure out when to stop the logger thread exactly as I don't know when the main thread stops. Is there a mechanism in C# that would send a notification when the main thread stops? (Or can you think about another solution to my problem?)

// This class automatically applies on each call of every method of Main() function
public class CommandLoggingAdvice : IMethodInterceptor
{
    private static ProducerConsumerClass LoggingQueue = ProducerConsumerClass.Instance;    
                LoggingQueue.AddTask("Logging message on call of *method*");            
}

public sealed class ProducerConsumerClass
{
    // here Iget an instance of log4net
    private ILog _Logger = null;
    protected ILog Logger
    {
        _Logger = LogManager.GetLogger("Logger1");            
    }

    private BlockingCollection<string> tasks = new BlockingCollection<string>();

    private static volatile ProducerConsumerClass _instance;     
    Thread worker;
    private Thread mainthread;

    private ProducerConsumerClass()
    {
        mainthread = Thread.CurrentThread;
        worker = new Thread(Work);
        worker.Name = "Queue thread";
        worker.IsBackground = false;
        worker.Start(mainthread);
    }

    public static ProducerConsumerClass Instance
    {
       get
       {
            if (_instance == null)       
            {
                _instance = new ProducerConsumerClass();
            }
       }
    }

    public void AddTask(string task)
    {
        tasks.Add(task);           
    }


    void Work(object mainthread)
    {
        Thread ma = (Thread) mainthread;
        if(ma.ThreadState != ThreadState.Stopped)
        {
             tasks.CompleteAdding();
        }
        while (true)
        {
            string task = null;
            if (!tasks.IsCompleted)
            {
                task = tasks.Take();
                Logger1.Info(task);   
            }
            else
            {
                return;
            }              
        }
    }
}

If BlockingCollection is empty and application is still working, loop calls one more Take() and result: logger thread is paused now. So when main thread.Threadstate == Stopped, i need to kill logger thread

More info about issue were added in comments

hyWhy
  • 23
  • 5
  • Is "your" thread part of the same application the main thread is running? – DerApe Jul 17 '14 at 12:25
  • Yes, that's right, and IsBackground = false – hyWhy Jul 17 '14 at 12:25
  • 2
    How exactly does your main thread terminates? – Sriram Sakthivel Jul 17 '14 at 12:26
  • as usual... all work is done and i call return 0; – hyWhy Jul 17 '14 at 12:27
  • You need anything specific when stopping your logger thread, or you just want it to terminate too? – ken2k Jul 17 '14 at 12:27
  • @derape Threads are not specific to app domain. – Sriram Sakthivel Jul 17 '14 at 12:29
  • @ken2k My applicaion has IsBackground=false, in which case the logging thread doesn't terminate when the application is about to terminate. I somehow need to signal the logging thread to exit gracefully when the main thread stops. – hyWhy Jul 17 '14 at 12:36
  • 2
    Please add code that shows how you launch your background thread. Also this questions sound a lot like http://stackoverflow.com/questions/1368697/how-to-detect-when-main-thread-terminates – Black Frog Jul 17 '14 at 12:37
  • @BlackFrog I've just updated the question. Please, see my example. – hyWhy Jul 17 '14 at 12:57
  • @hyWhy How much time do you think you would need to "gracefully shutdown"? Would it be more than a second or two? – Scott Chamberlain Jul 17 '14 at 13:13
  • @ScottChamberlain no, logger thread is not time-dependent, it depends on blockingcollection.Count == 0 or != 0; if Count != 0 - then i catch mainthread.ThreadState == Stopped and logger thread terminates automatically after emptying the queue. But if blockingcollection.Count=0 (which means that logger thread is paused), and after that main thread terminates... - that is a problem, because logger thread is constantly paused – hyWhy Jul 17 '14 at 13:21

1 Answers1

2

You already have code in the thread that exits if the BlockingCollection is empty and marked as completed. Your loop checks for IsCompleted and exits.

What you need is some way for the main thread to call CompleteAdding on the collection. I would recommend a public method in your ProducerConsumerClass:

public void AllDone()
{
    tasks.CompleteAdding();
}

So the main thread can call AllDone when it's done processing. Your thread will then empty the queue and exit.

By the way, a more concise way to write your logging loop is:

foreach (string task in tasks.GetConsumingEnumerable())
{
    Logger1.Info(task);
}

This also makes it easier to add cancellation support in the future.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • Thank you for your answer! It was the first solution in my mind, but there were an incapsulation issue: all work with blockingCollection is focused in one place: CommandLoggingAdvice class; and also if there is one developer who only adds logging classes to one or other Program(if he is a logging specialist and his only responsibility is to write logging classes), this will cause problem to him: he would have to change program code of the Program, so there won't be independent logging classes... – hyWhy Jul 17 '14 at 17:16
  • About "foreach" construction - i am not sure that after "taking" last element in queue, there will be one more take, which cause pause of logging thread(so the thread wouldn't use CPU time" – hyWhy Jul 17 '14 at 17:20
  • And about CancellationToken... It would help me to solve the problem, but i still can not catch the moment, the place and the way, how to use it: because logging thread is paused and i am not able to call Token method from it, the mainthread is about to be terminated and i don't know how to catch any terminating events from singleton class; CommonLoggingClass is implementation of Spring.AOP framework: it is only waits for calling of functions from Main() function - so CommonLoggingClass doesn't know when mainthread being terminated. – hyWhy Jul 17 '14 at 17:28
  • I've tried to set Logging thread property IsBackground = true and use Singleton Dispose/Finalize methods, which in this case are being called automatically right after terminating of mainthread... And Dispose { tasks.Completeadding; worker.Join;} - there were an idea that i could force mainthread to wait background logging thread... But that doesn't work – hyWhy Jul 17 '14 at 17:31
  • So for now i have two so called "bad solutions" of my problem: 1) I create in the Work(object mainthread) method new parallel thread which waits until mainthread is terminated: mainthread.Join() and calls Tasks.IsCompleted() and tasks.Dispose() 2) i dont pause logging thread till the event: i use now TryTake and TryAdd, and loggingthread.sleep(250) - i pause thread on tenth of seconds and then it awakes and checks if it should be terminated – hyWhy Jul 17 '14 at 17:48
  • Now i am thinking about use of SynchronizationContexts... Are there any good solutions for my problem??? – hyWhy Jul 17 '14 at 17:49
  • @hyWhy: The `foreach` I showed with `GetConsumingEnumerable` is the recommended way of using `BlockingCollection`. It does a non-busy wait for the next item, and exits when the queue is completed. – Jim Mischel Jul 17 '14 at 18:19
  • @hyWhy: Your other problem--wanting the loggers to shut down cleanly at exit without an explicit shutdown call--is a pretty typical problem indicative of an over-reliance on the Singleton pattern, which in many cases (including yours, it seems) is just a fancy way of saying "global variables." Perhaps you should have all of your loggers register themselves with a main logging class that keeps track of all loggers. The main program can then tell the main logging class that it's shutting down, and that class can then call the `Shutdown` method on each logger. – Jim Mischel Jul 17 '14 at 18:23
  • thank you for GetConsumingEnumerable method... but still there is no solution, where CompleteAdding() method should be called. About dividing one Singleton class into a lot of calls of Logger object in all classes of program... Then again the whole concept of separtion of logging concern into independent library (for example Singleton class+CommonLoggingClass) fails – hyWhy Jul 18 '14 at 05:12