2

Recently, I was asked to implement a class as part of a selection process. I did the program as requested. However, I failed in the test. I am really curious to know what is wrong in my solution. Any help is much appreciated. The question and my solution are given below

Question:

Implement a thread safe class which fires an event every second from construction. There need to be a function for finding the seconds elapsed. This class has to implement IDisposable and any calls to seconds elapsed function after calling dispose should fail.

My solution:

namespace TimeCounter
{
public delegate void SecondsElapsedHandler(object o, EventArgs e);
/// <summary>
/// Summary description for SecondCounter
/// </summary>
public class SecondCounter : IDisposable
{
    private volatile int nSecondsElapsed;
    Timer myTimer;
    private readonly object EventLock = new object();
    private SecondsElapsedHandler secondsHandler;
    public SecondCounter()
    {
        nSecondsElapsed = 0;
        myTimer = new Timer();
        myTimer.Elapsed += new ElapsedEventHandler(OneSecondElapsed);
        myTimer.Interval = 1000;
        myTimer.AutoReset = false;
        myTimer.Start();
    }

    public void OneSecondElapsed(object source, ElapsedEventArgs e)
    {
        try
        {
            SecondsElapsedHandler handlerCopy;
            lock (EventLock)
            {
                handlerCopy = secondsHandler;
                nSecondsElapsed++;

            }
            if (secondsHandler != null)
            {
                secondsHandler(this, e);
            }
        }
        catch (Exception exp)
        {
            Console.WriteLine("Exception thrown from SecondCounter OneSecondElapsed " + exp.Message);
        }
        finally
        {
            if (myTimer != null)
            {
                myTimer.Enabled = true;
            }
        }
    }

    public event SecondsElapsedHandler AnotherSecondElapsed
    {
        add
        {
            lock (EventLock)
            {
                secondsHandler += value;
            }
        }
        remove
        {
            lock (EventLock)
            {
                secondsHandler -= value;
            }

        }
    }

    public int SecondsElapsed()
    {
        if (this.IsDisposed)
        {
            throw new ObjectDisposedException("SecondCounter");
        }
        return nSecondsElapsed;

    }

    private bool IsDisposed = false;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool Disposing)
    {
        if (!IsDisposed)
        {
            if (Disposing)
            {

            }
            if (myTimer != null)
            {
                myTimer.Dispose();
            }

        }
        secondsHandler = null;
        IsDisposed = true;

    }
    ~SecondCounter()
    {
        Dispose(false);
    }
}
}
Jimmy
  • 3,224
  • 5
  • 29
  • 47
  • 1
    Were you told anything else other than that you failed? Was any rationale given for the decision to fail you? – Mark Byers Nov 02 '10 at 20:48
  • BFree, Sorry for missing that. I edited the original post with dispose implementation. – Jimmy Nov 02 '10 at 20:51

2 Answers2

2

There are a few problems:

  1. You might have been penalized for general Exception swallowing though that's not specifically related to threading issues.

  2. There's a race condition on your timer.Dispose, as you could Dispose the timer before it is set Enabled again, resulting in an Exception.

  3. You never set myTimer to null in Dispose.

  4. You're accessing the managed class myTimer from the finalizer (disposing=false), which is a bad idea.

  5. The explicit implementation of the event with locking is unnecessary. Delegates are immutable and adding/removing an event will never result in an invalid delegate state, though there can be race conditions if delegates are added/removed around the same time that the callback is fired. If you use the standard 'public event' declaration without an explicit backing private delegate, the synchronization will be handled automatically.

  6. (minor point) If you're implementing the full Dispose pattern, it's customary to mark the Dispose(bool disposing) method as protected virtual, so that deriving classes can hook into the disposal mechanism. Better yet, mark your class sealed and you can eliminate the finalizer entirely.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • That's a great review Dan. Thank you. – Jimmy Nov 02 '10 at 21:52
  • @Jimmy, glad to help. I _still_ mess up 'thread-safe' code regularly and try to write as little of it as possible. The best way to get thread-safety is to avoid having threads interact in the first place. – Dan Bryant Nov 02 '10 at 22:26
  • @Dan Bryant: Why do you think locking on events is unnecessary? Delegates may be immutable, but that doesn't imply that += and -= are atomic. If two threads simultaneously attempt to remove subscriptions, they might both grab the old delegate, remove themselves to from, and write it back; the second removal attempt would generate a permanent wrong subscription. One could use CompareExchange spinloops instead of locks; this could be faster if subscription changes are rare, but could logjam if they are too frequent. – supercat Nov 04 '10 at 15:42
  • @supercat, the compiler has special support for synchronizing the event add and remove, so you don't need to write this yourself. The exception is that, in versions prior to .NET 4.0, the synchronization will not occur if you subscribe to the event from within the class itself. In particular, the auto-generated event code adorns the event accessors with `[MethodImpl(MethodImplOptions.Synchronized)]`. The main downside to this default implementation is that it does the locking on 'this'. – Dan Bryant Nov 04 '10 at 16:00
  • @Dan Bryant: I may be confusing VB and C#, but I thought what the code here was doing was replacing the default add/remove handlers with custom ones (and the += notion simply meant 'delegate.combine'. What would one do in C# if one didn't want to have a lock on 'this'? In any case, if one is using .net prior to 4.0 and if any local code might now or in future add or remove subscriptions, that would sound like a good reason not to trust the built-in locking. – supercat Nov 04 '10 at 18:00
  • @supercat, when applied to delegates, the += just performs Combine, but when applied to events, it uses the event add logic. The default implementation of event add/remove (if you don't specify your own) implicitly wraps add and remove in a lock(this). If you don't want the locking, you have to explicitly define add/remove. – Dan Bryant Nov 04 '10 at 20:27
  • @Dan Bryant: Isn't secondshandler a delegate, rather than an event, used in an explicit implementation of event add/remove? – supercat Nov 04 '10 at 21:10
  • @supercat, in the OP's implementation, yes, as he made it an explicit private delegate field to allow the custom add/remove logic. If you expose the public event (which is fairly typical), the locking occurs implicitly. You're right, though, I didn't make that very clear; I'll amend my recommendations. – Dan Bryant Nov 04 '10 at 21:33
0

Your finalizer is probably broken. It correctly passes false as the Disposing parameter. This should tell Dispose(bool) to avoid attempting to dispose other managed objects. But in that method you put:

if (Disposing)
{

}
if (myTimer != null)
{
    myTimer.Dispose();
}

So you ignore the value of Disposing. This means that you call the timer's Dispose method from the finalizer thread, when that object may already have been finalized (if it has a finalizer, which it probably does). Finalizers run in an unpredictable order. It's generally recommended to not make calls to other GC-managed objects from a finalizer.

In fact, it's usually recommended that you don't write finalizers at all these days. The question didn't ask you to write one! It's unfortunate that most tutorials about IDisposable also talk about finalizers. They're different subjects.

You also catch Exception, the universal exception base class. This means you catch things like NullReferenceException. Not usually a good idea. You also log to the console, which is not worth much in a GUI or server-based application.

You can replace:

myTimer.Elapsed += new ElapsedEventHandler(OneSecondElapsed);

with:

myTimer.Elapsed += OneSecondElapsed;

Your variable naming is inconsistent. Refer to the Microsoft guidelines.

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
  • //In fact, it's usually recommended that you don't write finalizers at all these days// Is there any reason why one should //ever// add a finalizer to a base class which isn't explicitly designed for finalization? Even if one wants to log a cleanup failure, I would think that would be better handled by having the class create a finalizable object to which it holds the only reference. – supercat Nov 04 '10 at 15:44