当社のウェブサイトには、「config.aspx」などの構成ページがあり、ページの初期化時に構成ファイルからいくつかの情報が読み込まれます。ロードされた情報をキャッシュするために、ファクトリクラスを提供し、ファクトリのパブリックメソッドを呼び出して、ページがロードされたときに構成インスタンスを取得します。ただし、アプリケーションプールを再起動すると、イベントログに次のようなエラーメッセージが表示されることがあります。
メッセージ:オブジェクト参照がオブジェクトのインスタンスに設定されていません。 スタック:System.Collections.Generic.Dictionary`2.Insert(TKey key、TValue value、Boolean add) at System.Collections.Generic.Dictionary`2.set_Item(TKey key、TValue value) at ObjectFactory.GetInstance(string key) at config.Page_Load(Object sender、EventArgs e) at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp、Object o、Object t、EventArgs e) at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender、EventArgs e) at System.Web.UI.Control.OnLoad(EventArgs e) at System.Web.UI.Control.LoadRecursive() atSystem.Web.UI.Page。 ProcessRequestMain(ブール値includeStagesBeforeAsyncPoint、ブール値includeStagesAfterAsyncPoint)
ファクトリクラスは次のように実装します。
public static class ObjectFactory
{
private static object _InternalSyncObject;
private static Dictionary _Instances;
private static object InternalSyncObject
{
get
{
if (_InternalSyncObject == null)
{
var @object = new object();
Interlocked.CompareExchange(ref _InternalSyncObject, @object, null);
}
return _InternalSyncObject;
}
}
private static Dictionary Instances
{
get
{
if (_Instances == null)
{
lock (InternalSyncObject)
{
if (_Instances == null)
{
_Instances = new Dictionary();
}
}
}
return _Instances;
}
}
private static object LoadInstance(string key)
{
object obj = null;
// some statements to load an specific instance from a configuration file.
return obj;
}
public static object GetInstance(string key)
{
object instance;
if (false == Instances.TryGetValue(key, out instance))
{
instance = LoadInstance(key);
Instances[key] = instance;
}
return instance;
}
}
辞書のset_Item
メソッドを呼び出すことができる唯一のコードであるため、例外は「Instances [key] = instance;」という行によってスローされたと思います。ただし、「Instances」値がnullの場合、NullReferenceException
メソッドを呼び出すとTryGetValue
がスローされ、スタックトレースのトップフレームはGetInstance
ではなくInsert
である必要があります。マルチスレッドシナリオでset_Item
メソッドを呼び出すときに、辞書がNullReferenceException
をスローする方法を知っている人はいますか?
例外はDictionary
コードの内部で発生するため、複数のスレッドから同時に同じDictionary
インスタンスにアクセスしていることを意味します。
GetInstance
メソッドのコードを同期して、一度に1つのスレッドだけがDictionary
にアクセスするようにする必要があります。
編集:
アクセスを個別にロックして、(おそらく)時間のかかるロードを実行しているときにロックの内側に入らないようにします。
private static object _sync = new object();
public static object GetInstance(string key) {
object instance = null;
bool found;
lock (_sync) {
found = Instances.TryGetValue(key, out instance);
}
if (!found) {
instance = LoadInstance(key);
lock (_sync) {
object current;
if (Instances.TryGetValue(key, out current)) {
// some other thread already loaded the object, so we use that instead
instance = current;
} else {
Instances[key] = instance;
}
}
}
return instance;
}
.Net 4の時点で、スレッドセーフな辞書である ConcurrentDictionary があり、「手動」同期は不要です。
引用するには http://msdn.Microsoft.com/en-us/library/xfhwa508.aspx (私が強調を追加):
「」スレッドセーフ
このタイプのパブリック静的(Visual Basicで共有)メンバーはスレッドセーフです。インスタンスメンバーは、スレッドセーフであることが保証されていません。
Dictionary<(Of <(TKey, TValue>)>)
は、コレクションが変更されていない限り、複数のリーダーを同時にサポートできます。それでも、コレクションを介して列挙することは、本質的にスレッドセーフな手順ではありません。列挙型が書き込みアクセスと競合するまれなケースでは、列挙型全体でコレクションをロックする必要があります。 読み取りと書き込みのために複数のスレッドがコレクションにアクセスできるようにするには、独自の同期を実装する必要があります。」
より良い解決策は、同期された辞書を作成することです。これがこの状況で機能するものです。 ReaderWriterLockSlimは、この状況で使用するのに最適な同期オブジェクトだと思います。辞書への書き込みは非常にまれです。ほとんどの場合、キーは辞書にあります。私は辞書のすべてのメソッドを実装したわけではなく、この場合に使用されたメソッドだけを実装したため、完全に同期された辞書ではありません。
public sealed class SynchronizedDictionary<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();
private readonly ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim();
public TValue this[TKey key]
{
get
{
readerWriterLock.EnterReadLock();
try
{
return this.dictionary[key];
}
finally
{
readerWriterLock.ExitReadLock();
}
}
set
{
readerWriterLock.EnterWriteLock();
try
{
this.dictionary[key] = value;
}
finally
{
readerWriterLock.ExitWriteLock();
}
}
}
public bool TryGetValue(TKey key, out TValue value)
{
readerWriterLock.EnterReadLock();
try
{
return this.dictionary.TryGetValue(key, out value);
}
finally
{
readerWriterLock.ExitReadLock();
}
}
}
あなたのInstances
Dictionary
はnullではないと思います。例外はInsert
メソッドの内部からです-実行時にDictionary
オブジェクトがあることを意味します(また、あなたが言ったように、あなたはすでにTryGetValue
を以前に持っていました同じ参照)key
がnullである可能性がありますか?
チェックしたばかりです-TryGetValue
は、nullキーを受信するとArgumentNullExceptionをスローし、nullキーを使用して挿入します。しかし、あなたの例ではどのクラスを使用していますか?ジェネリックIDictionary<string, string>
を使用しましたが、非ジェネリックを使用しているようです。 DictionaryBase
から継承したクラスですか、それともHashTable
から継承したクラスですか?