This is frustrating, but I've noticed one of the biggest problems generally stems from multi-threading. Most developers aren't tuned to developing multi-threaded applications. Even when they are, it's just another thing to watch out for when developing code.
So, you may be writing an application in WPF. Say, it subscribes to an asynchronous TCP socket at the application level. You have a callback on a user control. Is there a potential problem?
YES.
Any time you have another resource that can act of its own accord and interface with another thread (events to the user interface, for example), you have a chance of having a Schrödinbug! For example, at work I had a strange issue -- a control would hit the end of its life-cycle. Disposed and all was well, right? Well, not quite. I had a DispatcherTimer that was syncing up some things for that control. I stopped it in Dispose -- so the timer shouldn't be called, right?
Wrong.
The timer did stop -- but it had already entered its callback on the event thread. The thread then swapped back to handle the Dispose that I had called. It finished the Dispose, then went back to the event callback to finish. Surprise surprise! The control had been disposed and threw an exception. So, how do we get around this?
I've developed a fairly simple pattern... using a lock and the basic Disposable pattern. I'm attaching the basic skeleton on how to implement this in your code:
public class MyClass : IDisposable { private object _lock = new object(); #region IDisposable Pattern private Boolean _disposed = false; private void OnEventCallback(object sender, EventArgs e) { lock(_lock) { if( _disposed) return; // Handle callback here } } /// <summary> /// When called, throws an ObjectDisposedException if the object has been disposed. /// </summary> protected virtual void CheckDisposed() { if (_disposed) { throw new ObjectDisposedException(this.GetType().Name); } } /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Core dispose methods /// </summary> /// <param name="disposing">True if called from IDisposable::Dispose</param> protected virtual void Dispose(bool disposing) { lock (_lock) { if (disposing && !_disposed) { // Clean up managed resources here } _disposed = true; } } /// <summary> /// Releases unmanaged resources and performs other cleanup operations before the /// <see cref="MyClass"/> is reclaimed by garbage collection. /// </summary> ~MyClass() { Dispose(false); } #endregion }
Notice the lock around the two most important parts -- I wanted to make sure that I have critical sections on the _disposed, such that I can break out as necessary. Notice, if it enters the thread for the callback, we're fine. If it disposes on the other thread, we can still break out before the code hits something disposed. Essentially, we're making textbook critical sections and managing them appropriately.
Nothing unusual or entirely special -- just adapted to be mindful of another thread.
No comments:
Post a Comment