226 lines
13 KiB
C#
226 lines
13 KiB
C#
|
// <copyright file="ChangeMonitor.cs" company="Microsoft">
|
||
|
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
using System;
|
||
|
using System.Runtime.Caching.Resources;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Threading;
|
||
|
|
||
|
// Every member of this class is thread-safe.
|
||
|
//
|
||
|
// Derived classes begin monitoring during construction, so that a user can know if the
|
||
|
// dependency changed any time after construction. For example, suppose we have a
|
||
|
// FileChangeMonitor class that derives from ChangeMonitor. A user might create an instance
|
||
|
// of FileChangeMonitor for an XML file, and then read the file to populate an object representation.
|
||
|
// The user would then cache the object with the FileChangeMonitor. The user could optionally check the
|
||
|
// HasChanged property of the FileChangeMonitor, to see if the XML file changed while the object
|
||
|
// was being populated, and if it had changed, they could call Dispose and start over, without
|
||
|
// inserting the item into the cache. However, in a multi-threaded environment, for cleaner, easier
|
||
|
// to maintain code, it's usually appropriate to just insert without checking HasChanged, since the
|
||
|
// cache implementer will handle this for you, and the next thread to attempt to get the object
|
||
|
// will recreate and insert it.
|
||
|
//
|
||
|
// The following contract must be followed by derived classes, cache implementers, and users of the
|
||
|
// derived class:
|
||
|
//
|
||
|
// 1. The constructor of a derived class must set UniqueId, begin monitoring for dependency
|
||
|
// changes, and call InitializationComplete before returning. If a dependency changes
|
||
|
// before initialization is complete, for example, if a dependent cache key is not found
|
||
|
// in the cache, the constructor must invoke OnChanged. The constructor can only call
|
||
|
// Dispose after InitializationComplete is called, because Dispose will throw
|
||
|
// InvalidOperationException if initialization is not complete.
|
||
|
// 2. Once constructed, the user must either insert the ChangeMonitor into an ObjectCache, or
|
||
|
// if they're not going to use it, they must call Dispose.
|
||
|
// 3. Once inserted into an ObjectCache, the ObjectCache implementation must ensure that the
|
||
|
// ChangeMonitor is eventually disposed. Even if the insert is invalid, and results in an
|
||
|
// exception being thrown, the ObjectCache implementation must call Dispose. If this we're not
|
||
|
// a requirement, users of the ChangeMonitor would need exception handling around each insert
|
||
|
// into the cache that carefully ensures the dependency is disposed. While this would work, we
|
||
|
// think it is better to put this burden on the ObjectCache implementer, since users are far more
|
||
|
// numerous than cache implementers.
|
||
|
// 4. After the ChangeMonitor is inserted into a cache, the ObjectCache implementer must call
|
||
|
// NotifyOnChanged, passing in an OnChangedCallback. NotifyOnChanged can only be called once,
|
||
|
// and will throw InvalidOperationException on subsequent calls. If the dependency has already
|
||
|
// changed, the OnChangedCallback will be called when NotifyOnChanged is called. Otherwise, the
|
||
|
// OnChangedCallback will be called exactly once, when OnChanged is invoked or when Dispose
|
||
|
// is invoked, which ever happens first.
|
||
|
// 5. The OnChangedCallback provided by the cache implementer should remove the cache entry, and specify
|
||
|
// a reason of CacheEntryRemovedReason.DependencyChanged. Care should be taken to remove the specific
|
||
|
// entry having this dependency, and not it's replacement, which will have the same key.
|
||
|
// 6. In general, it is okay for OnChanged to be called at any time. If OnChanged is called before
|
||
|
// NotifyOnChanged is called, the "state" from the original call to OnChanged will be saved, and the
|
||
|
// callback to NotifyOnChange will be called immediately when NotifyOnChanged is invoked.
|
||
|
// 7. A derived class must implement Dispose(bool disposing) to release all managed and unmanaged
|
||
|
// resources when "disposing" is true. Dispose(true) is only called once, when the instance is
|
||
|
// disposed. The derived class must not call Dispose(true) directly--it should only be called by
|
||
|
// the ChangeMonitor class, when disposed. Although a derived class could implement a finalizer and
|
||
|
// invoke Dispose(false), this is generally not necessary. Dependency monitoring is typically performed
|
||
|
// by a service that maintains a reference to the ChangeMonitor, preventing it from being garbage collected,
|
||
|
// and making finalizers useless. To help prevent leaks, when a dependency changes, OnChanged disposes
|
||
|
// the ChangeMonitor, unless initialization has not yet completed.
|
||
|
// 8. Dispose() must be called, and is designed to be called, in one of the following three ways:
|
||
|
// - The user must call Dispose() if they decide not to insert the ChangeMonitor into a cache. Otherwise,
|
||
|
// the ChangeMonitor will continue monitoring for changes and be unavailable for garbage collection.
|
||
|
// - The cache implementor is responsible for calling Dispose() once an attempt is made to insert it.
|
||
|
// Even if the insert throws, the cache implementor must dispose the dependency.
|
||
|
// Even if the entry is removed, the cache implementor must dispose the dependency.
|
||
|
// - The OnChanged method will automatically call Dispose if initialization is complete. Otherwise, when
|
||
|
// the derived class' constructor calls InitializationComplete, the instance will be automatically disposed.
|
||
|
//
|
||
|
// Before inserted into the cache, the user must ensure the dependency is disposed. Once inserted into the
|
||
|
// cache, the cache implementer must ensure that Dispose is called, even if the insert fails. After being inserted
|
||
|
// into a cache, the user should not dispose the dependency. When Dispose is called, it is treated as if the dependency
|
||
|
// changed, and OnChanged is automatically invoked.
|
||
|
// 9. HasChanged will be true after OnChanged is called by the derived class, regardless of whether an OnChangedCallback has been set
|
||
|
// by a call to NotifyOnChanged.
|
||
|
|
||
|
namespace System.Runtime.Caching {
|
||
|
public abstract class ChangeMonitor : IDisposable {
|
||
|
private const int INITIALIZED = 0x01; // initialization complete
|
||
|
private const int CHANGED = 0x02; // dependency changed
|
||
|
private const int INVOKED = 0x04; // OnChangedCallback has been invoked
|
||
|
private const int DISPOSED = 0x08; // Dispose(true) called, or about to be called
|
||
|
private readonly static object NOT_SET = new object();
|
||
|
|
||
|
private SafeBitVector32 _flags;
|
||
|
private OnChangedCallback _onChangedCallback;
|
||
|
private Object _onChangedState = NOT_SET;
|
||
|
|
||
|
// The helper routines (OnChangedHelper and DisposeHelper) are used to prevent
|
||
|
// an infinite loop, where Dispose calls OnChanged and OnChanged calls Dispose.
|
||
|
[SuppressMessage("Microsoft.Performance", "CA1816:DisposeMethodsShouldCallSuppressFinalize", Justification = "Grandfathered suppression from original caching code checkin")]
|
||
|
private void DisposeHelper() {
|
||
|
// if not initialized, return without doing anything.
|
||
|
if (_flags[INITIALIZED]) {
|
||
|
if (_flags.ChangeValue(DISPOSED, true)) {
|
||
|
Dispose(true);
|
||
|
GC.SuppressFinalize(this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The helper routines (OnChangedHelper and DisposeHelper) are used to prevent
|
||
|
// an infinite loop, where Dispose calls OnChanged and OnChanged calls Dispose.
|
||
|
private void OnChangedHelper(Object state) {
|
||
|
_flags[CHANGED] = true;
|
||
|
|
||
|
// the callback is only invoked once, after NotifyOnChanged is called, so
|
||
|
// remember "state" on the first call and use it when invoking the callback
|
||
|
Interlocked.CompareExchange(ref _onChangedState, state, NOT_SET);
|
||
|
|
||
|
OnChangedCallback onChangedCallback = _onChangedCallback;
|
||
|
if (onChangedCallback != null) {
|
||
|
// only invoke the callback once
|
||
|
if (_flags.ChangeValue(INVOKED, true)) {
|
||
|
onChangedCallback(_onChangedState);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// protected members
|
||
|
//
|
||
|
|
||
|
// Derived classes must implement this. When "disposing" is true,
|
||
|
// all managed and unmanaged resources are disposed and any references to this
|
||
|
// object are released so that the ChangeMonitor can be garbage collected.
|
||
|
// It is guaranteed that ChangeMonitor.Dispose() will only invoke
|
||
|
// Dispose(bool disposing) once.
|
||
|
protected abstract void Dispose(bool disposing);
|
||
|
|
||
|
// Derived classes must call InitializationComplete
|
||
|
protected void InitializationComplete() {
|
||
|
_flags[INITIALIZED] = true;
|
||
|
|
||
|
// If the dependency has already changed, or someone tried to dispose us, then call Dispose now.
|
||
|
Dbg.Assert(_flags[INITIALIZED], "It is critical that INITIALIZED is set before CHANGED is checked below");
|
||
|
if (_flags[CHANGED]) {
|
||
|
Dispose();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Derived classes call OnChanged when the dependency changes. Optionally,
|
||
|
// they may pass state which will be passed to the OnChangedCallback. The
|
||
|
// OnChangedCallback is only invoked once, and only after NotifyOnChanged is
|
||
|
// called by the cache implementer. OnChanged is also invoked when the instance
|
||
|
// is disposed, but only has an affect if the callback has not already been invoked.
|
||
|
protected void OnChanged(Object state) {
|
||
|
OnChangedHelper(state);
|
||
|
|
||
|
// OnChanged will also invoke Dispose, but only after initialization is complete
|
||
|
Dbg.Assert(_flags[CHANGED], "It is critical that CHANGED is set before INITIALIZED is checked below.");
|
||
|
if (_flags[INITIALIZED]) {
|
||
|
DisposeHelper();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// public members
|
||
|
//
|
||
|
|
||
|
// set to true when the dependency changes, specifically, when OnChanged is called.
|
||
|
public bool HasChanged { get { return _flags[CHANGED]; } }
|
||
|
|
||
|
// set to true when this instance is disposed, specifically, after
|
||
|
// Dispose(bool disposing) is called by Dispose().
|
||
|
public bool IsDisposed { get { return _flags[DISPOSED]; } }
|
||
|
|
||
|
// a unique ID representing this ChangeMonitor, typically consisting of
|
||
|
// the dependency names and last-modified times.
|
||
|
public abstract string UniqueId { get; }
|
||
|
|
||
|
|
||
|
// Dispose must be called to release the ChangeMonitor. In order to
|
||
|
// prevent derived classes from overriding Dispose, it is not an explicit
|
||
|
// interface implementation.
|
||
|
//
|
||
|
// Before cache insertion, if the user decides not to do a cache insert, they
|
||
|
// must call this to dispose the dependency; otherwise, the ChangeMonitor will
|
||
|
// be referenced and unable to be garbage collected until the dependency changes.
|
||
|
//
|
||
|
// After cache insertion, the cache implementer must call this when the cache entry
|
||
|
// is removed, for whatever reason. Even if an exception is thrown during insert.
|
||
|
//
|
||
|
// After cache insertion, the user should not call Dispose. However, since there's
|
||
|
// no way to prevent this, doing so will invoke the OnChanged event handler, if it
|
||
|
// hasn't already been invoked, and the cache entry will be notified as if the
|
||
|
// dependency has changed.
|
||
|
//
|
||
|
// Dispose() will only invoke the Dispose(bool disposing) method of derived classes
|
||
|
// once, the first time it is called. Subsequent calls to Dispose() perform no
|
||
|
// operation. After Dispose is called, the IsDisposed property will be true.
|
||
|
[SuppressMessage("Microsoft.Performance", "CA1816:DisposeMethodsShouldCallSuppressFinalize", Justification = "Grandfathered suppression from original caching code checkin")]
|
||
|
public void Dispose() {
|
||
|
OnChangedHelper(null);
|
||
|
|
||
|
// If not initialized, throw, so the derived class understands that it must call InitializeComplete before Dispose.
|
||
|
Dbg.Assert(_flags[CHANGED], "It is critical that CHANGED is set before INITIALIZED is checked below.");
|
||
|
if (!_flags[INITIALIZED]) {
|
||
|
throw new InvalidOperationException(R.Init_not_complete);
|
||
|
}
|
||
|
|
||
|
DisposeHelper();
|
||
|
}
|
||
|
|
||
|
// Cache implementers must call this to be notified of any dependency changes.
|
||
|
// NotifyOnChanged can only be invoked once, and will throw InvalidOperationException
|
||
|
// on subsequent calls. The OnChangedCallback is guaranteed to be called exactly once.
|
||
|
// It will be called when the dependency changes, or if it has already changed, it will
|
||
|
// be called immediately (on the same thread??).
|
||
|
public void NotifyOnChanged(OnChangedCallback onChangedCallback) {
|
||
|
if (onChangedCallback == null) {
|
||
|
throw new ArgumentNullException("onChangedCallback");
|
||
|
}
|
||
|
|
||
|
if (Interlocked.CompareExchange(ref _onChangedCallback, onChangedCallback, null) != null) {
|
||
|
throw new InvalidOperationException(R.Method_already_invoked);
|
||
|
}
|
||
|
|
||
|
// if it already changed, raise the event now.
|
||
|
if (_flags[CHANGED]) {
|
||
|
OnChanged(null);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|