Поточная безопасная ленивая загрузка, когда загрузка может завершиться - PullRequest
1 голос
/ 09 августа 2010

Я потратил около часа в поисках консенсуса на то, что я пытаюсь выполнить, но мне еще не удалось найти что-то убедительное в определенном направлении.

Моя ситуация такова:

  • У меня есть многопоточное приложение (веб-служба .NET)
  • У меня есть классы, которые используют объекты, для загрузки которых требуется ничтожное время, поэтому я бы хотел сохранить их как статическиечлены класса
  • Код, который создает эти объекты периодически, имеет низкую вероятность сбоя

Ранее я использовал подход, который создает эти объекты в статическом конструкторе.Проблема с этим заключалась в том, что, как упоминалось выше, конструктор иногда выходил из строя, и как только статический конструктор .NET терпел неудачу, весь класс оставался в стороне до тех пор, пока процесс не будет перезапущен.Второго шанса при таком подходе нет.

Наиболее интуитивно-понятный подход после этого заключался в использовании двойной проверки блокировки.Вокруг есть много страниц, которые рассказывают о пороках двойной проверки блокировки и говорят, что я использую статический конструктор, что я уже делал, но это не вариант для меня, так как статический конструктор имеетвозможность провалиться и обрушить весь класс.

Реализация (конечно, упрощенная), которую я думаю использовать, заключается в следующем.Все имена классов и членов носят чисто демонстрационный характер, а не то, что я на самом деле использую.Этот подход будет проблематичным?Кто-нибудь может предложить лучший подход?

public class LazyMembers
{
    private static volatile XmlDocument s_doc;
    private static volatile XmlNamespaceManager s_nsmgr;
    private static readonly object s_lock = new object();

    private static void EnsureStaticMembers()
    {
        if (s_doc == null || s_nsmgr == null)
        {
            lock (s_lock)
            {
                if (s_doc == null || s_nsmgr == null)
                {
                    // The following method might fail
                    // with an exception, but if it succeeds,
                    // s_doc and s_nsmgr will be initialized
                    s_doc = LoadDoc(out s_nsmgr);
                }
            }
        }
    }

    public XmlNamespaceManager NamespaceManager
    {
        get
        {
            EnsureStaticMembers();
            return s_nsmgr;
        }
    }

    public XmlDocument GetDocClone()
    {
        EnsureStaticMembers();
        return (XmlDocument)s_doc.Clone();
    }
}

1 Ответ

2 голосов
/ 09 августа 2010

Если вы используете .NET 4.0, вы можете ссылаться на Lazy<T> и LazyThreadSafetyMode (это зависит от того, хотите ли вы создать несколько экземпляров T или нет в многопоточной среде. В вашем случае вам нужно обратиться к Lazy<T>(Func<T> func, LazyThreadSafetyMode) конструктору - здесь (MSDN)

В противном случае (если вы используете 3,5 или ниже), вы можете использовать CAS techinque для создания одного экземпляра без блокировки.

Примерно так:

get {
    if( _instance == null) {
        var singleton = new Singleton();
        if(Interlocked.CompareExchange(ref _instance, singleton, null) != null) {
            if (singleton is IDisposable) singleton.Dispose();
        }
    }
    return _instance;
}

Однако здесь вы можете добиться только поведения LazyThreadSafetyMode.Publications - только один экземпляр будет виден другим потокам, но немногие могут быть созданы.

Кроме того, не должно быть никаких проблем с двойной проверкой на нулевое значение в вашем коде - это безопасно в мире .NET (по крайней мере, на машинах x86 и связанной модели памяти). До 2004 года в мире Java были некоторые проблемы, AFAIK.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...