С# Singleton Thread safe

У меня есть одноэлементный класс, похожий на этот

public class Singleton
{
    private static Singleton m_instance;
    private Timer m_timer;
    private static List<CustomObject> m_cacheObjects;

    private Singleton()
    {    
        m_cacheObjects = new List<CustomObject>();
        m_timer= new Timer(MyTimerCallBack, 
                           null, 
                           TimeSpan.FromSeconds(60), 
                           TimeSpan.FromSeconds(60));           
    }

    public static Singleton Instance
    {
        get
        {
            if (m_instance == null)
            {
                m_instance = new Singleton();
            }
            return m_instance;
        }
    }

    private void MyTimerCallBack(object state)
    {
        //******** Update the list by interval here ******************

        m_cacheObjects = UpdateTheList();
    }

    public void CallMe()
    {
        foreach (CustomObject obj in m_cacheObjects)
        {
            // do something here based on obj

            // The question is, does the m_cacheObjects is thread safe??
            // what happen if the m_cacheObjects is changed
            // during the loop interation?
        }
    }
}

Метод CallMe будет вызываться веб-службой:

  [WebMethod]
    public void CallMeWebService()
    {
        Singleton.Instance.CallMe();
    }

Вопросы: 1) Является ли m_cacheObjects потокобезопасным? что произойдет, если m_cacheObjects будет изменен (из-за таймера) во время интервала цикла (в CallMe())?

2) Будет ли новый поток создаваться при вызове Webservice CallMeWebService()?

Ответ 1

1: Нет, статический список не является автоматически потокобезопасным; вы должны защитить m_cacheObjects вручную

2: Это деталь реализации; на первый взгляд похоже, что он раскрывает себя как метод синхронизации, но как он это делает, полностью зависит от него.

Фактически, ваша статическая инициализация также не является потокобезопасной; Я мог бы скорректировать сценарий, в котором использовались два разных экземпляра Singleton. Для его выполнения потребуется повторение, но это произойдет.

Честно говоря, если у вас нет по-настоящему веской причины, простейший, но безопасный одноэлементный шаблон просто:

private static readonly Singleton m_instance = new Singleton();

Ответ 3

//using System.Runtime.CompilerServices;

private static volatile Singelton _instance;

public static Singelton Instance
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    get
    {
        if (_instance == null)
        {
            _instance = new Singelton();
        }
        return _instance;
    }
}

Объясняю:

[MethodImpl (MethodImplOptions.Synchronized)] Это сообщит компилятору, что доступ к "Экземпляру" "Синхронизирован", поэтому система заботится о вызовах этого параметра.

Это потокобезопасно.

EDIT: (Кроме того, примеры "Lock()" небезопасны! Coz, u может отключить поток Безопасность с помощью "Monitor.Exit(Singleton);" )

Ответ 4

Это довольно хороший ресурс о том, как реализовать одноэлементный шаблон в потоковом режиме: http://msdn.microsoft.com/en-us/library/ff650316.aspx

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
     if (instance == null) 
     {
        lock (syncRoot) 
        {
           if (instance == null) 
          instance = new Singleton();
        }
     }

     return instance;
      }
   }
}

Это просто гарантирует, что никогда не будет более одного экземпляра. Вам также необходимо применить блокировку для своего настраиваемого метода.

public void CallMe()
{
    lock (syncRoot) 
    {
        foreach (CustomObject obj in m_cacheObjects)
        {
            // do something here based on obj
        }
    }
}

Ответ 5

Он не является потокобезопасным.

Я полагаю, что использовал бы блокировку в методах "MyTimerCallBack" и "CallMe"

Ответ 6

1) Нет, m_cacheObjects не является потокобезопасным.

2) Да, будет создан новый поток (ну, может быть, это не новый поток, а поток, связанный с пулом потоков).

Вам нужно защитить m_cacheObjects оператором lock. Кроме того, в методе CallMe я рекомендую создать локальную копию m_cacheObjects:

// create new field syncRoot
private static readonly object syncRoot = new object();

Новый метод CallMe:

List<CustomObject> localCopy;
lock (syncRoot)
{
    localCopy = new List<CustomObject>(m_cacheObjects);
}

foreach (var nextObject in localCopy)
{
// Do some work
}

И обновленный метод MyTimerCallBack:

lock (syncRoot)
{
    m_cacheObjects = UpdateTheList();
}

И, пожалуйста, также создайте потокобезопасный Singleton (читайте другие ответы для деталей).