Подтвердить что ты не робот

Singleton Class, который требует некоторого асинхронного вызова

У меня есть Singleton Class, который загружает некоторые данные о его построении. Проблема в том, что для загрузки этих данных требуется вызвать методы async, но конструктор не может быть async.

Другими словами, мой класс имеет следующую структуру:

public class Singleton
{
   private static Singleton instance;

   private Singleton() 
   {
       LoadData();
   }

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

LoadData() - это функция async, которая вызывает множество функций async, а также инициализацию. Как я могу правильно вызвать LoadData(), чтобы все было правильно инициализировано?

4b9b3361

Ответ 1

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

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

Если асинхронные методы возвращают Task или Task<T>, вы всегда можете использовать продолжение задачи, чтобы установить свои данные в классе после завершения асинхронной операции или просто заблокировать результаты, в зависимости от того, что делает большинство смысла в вашем сценарии. Не зная требований к построению этого объекта, трудно понять, что подходит в этом сценарии.


Изменить:

Один из вариантов, учитывая перечисленные выше цели, заключается в изменении вашего объявления Singleton, чтобы метод для извлечения Instance был методом, а не свойством. Это позволит вам сделать асинхронным:

public class Singleton
{
   private static Singleton instance;

   private Singleton() 
   {
          // Don't load the data here - will be called separately
   }

   public static async Task<Singleton> GetInstance()
   {
         if (instance == null)
         {
            instance = new Singleton();
            await instance.LoadData();
         }

         return instance;
    }
}

Это позволит вам использовать await в вызове для фактического извлечения экземпляра. Самое приятное в этом заключается в том, что очень ясно, что вы вызываете асинхронную операцию, и вы получите правильную обработку результатов, так как результат вернется, как и любой другой метод async.

Помните, однако, что это не потокобезопасно (хотя оригинал тоже не был), поэтому, если вы собираетесь использовать этот Singleton из нескольких потоков, вам, возможно, придется переосмыслить общий дизайн.

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

Ответ 2

Решение для потокобезопасного, async singleton на самом деле супер просто, если мы разрешаем только внутренние механизмы класса Task!

Итак, как работает Task? Допустим, что у вас есть экземпляр Task<T>, а вы await - один раз. Теперь задача выполнена, и вам возвращается и возвращается значение T. Что делать, если вы снова await повторяете тот же экземпляр задачи? В этом случае задача просто возвращает ранее полученное значение немедленно полностью синхронно.

И что, если вы await один и тот же экземпляр задачи одновременно из нескольких потоков (где вы обычно получаете состояние гонки)? Ну, первая (так как там будет первая, которая туда попадет) выполнит код задачи, а остальные будут ждать обработки результата. Затем, когда результат будет получен, все await s закончат (практически) одновременно и вернут значение.

Таким образом, решение для синглета async, которое является потокобезопасным, на самом деле супер просто:

public class Singleton
{
    private static readonly Task<Singleton> _getInstanceTask = CreateSingleton();

    public static Task<Singleton> Instance
    {
        get { return _getInstanceTask; }
    }

    private Singleton(SomeData someData)
    {
        SomeData = someData;
    }

    public SomeData SomeData { get; private set; }

    private static async Task<Singleton> CreateSingleton()
    {
        SomeData someData = await LoadData();
        return new Singleton(someData);
    }
}

Теперь вы можете получить доступ к синглтону следующим образом:

Singleton mySingleton = await Singleton.Instance;

или

Singleton mySingleton = Singleton.Instance.Result;

или

SomeData mySingletonData = (await Singleton.Instance).SomeData;

или

SomeData mySingletonData = Singleton.Instance.Result.SomeData;

Подробнее здесь: Асинхронная инициализация синглтона

Ответ 3

Вы можете использовать асинхронную ленивую инициализацию для этого:

public class Singleton
{
  private static readonly AsyncLazy<Singleton> instance =
      new AsyncLazy<Singleton>(CreateAndLoadData);

  private Singleton() 
  {
  }

  // This method could also be an async lambda passed to the AsyncLazy constructor.
  private static async Task<Singleton> CreateAndLoadData()
  {
    var ret = new Singleton();
    await ret.LoadDataAsync();
    return ret;
  }

  public static AsyncLazy<Singleton> Instance
  {
    get { return instance; }
  }
}

И тогда вы можете использовать его следующим образом:

Singleton singleton = await Singleton.Instance;

Одним из преимуществ использования AsyncLazy<T> является то, что он является потокобезопасным. Однако имейте в виду, что он всегда выполняет свой делегат в потоке пула потоков.

Ответ 4

Ну, не имеет смысла, что вы хотите асинхронно инициализировать синглтон. Если вы просто хотите вызвать метод, который возвращает Task в вашей инициализации, вы можете просто сделать:

var task = MyAsyncMethod();
task.Wait();
return task.Result;

Без необходимости сделать метод async.

Но если вы хотите, чтобы значение singleton было заданием, вы можете использовать Lazy как таковой:

Lazy<Task<int>> l = new Lazy<Task<int>>(async () => { int i = await calculateNumber(); return i; });

Кроме того, Lazy<T> является предпочтительным методом реализации "синглетонов". Одиночные классы трудно получить право (или трудно поддерживать)...