2014-10-09 3 views
3

Предположим, что у меня есть класс с членами, для которых требуются асинхронные действия для инициализации (например, файлы ввода/вывода или веб-запросы). Мне нужно только инициализировать один раз, и я не хочу повторно инициализировать.Инициализировать Async Only Once Pattern

Задачи и Async-Await подходит для этого?

Вот пример того, что я сейчас делаю:

private Task _initializeTask; 
public Task InitializeAsync() 
{ 
    return _initializeTask ?? (_initializeTask = Task.Run(
      async() => 
      { 
       // Do an action requiring await here 
       await _storageField.LoadAsync(); 
      })); 
} 

ли это сделать то, что я думаю, что это делает? Есть ли лучшие способы сделать это?

Это поточный сейф? Это не требование, но его следует учитывать.

изменения:

То, что я думаю, что это делает? Я считаю, что если _initializeTask не был назначен, ему будет назначена новая задача, которая начнется, а затем ждет асинхронную лямбду, содержащуюся внутри. Любые последующие вызовы метода ждут уже запущенной (или завершенной) задачи, которая была назначена _initializedTask.

Когда я хочу его построить? Обычно я использую этот метод для службы, которую я разрешаю с контейнером IoC. Несколько зависимых классов могут быть построены со ссылкой на класс. Затем, перед использованием, каждый из них ожидает InitializeAsync(). Если theres несколько зависимых классов, то я не хочу удваивать его при инициализации.

Заводской метод? Обычно не нужно создавать несколько экземпляров, которые должны быть инициализированы, поэтому метод Factory не кажется хорошим решением. Я использовал что-то вроде статического метода CreateAsync() для таких вещей, как классы оболочки папок, но это не позволяло мне вводить инициализированные папки в конструкторы. Методы Async Factory ничего не получают, если они не могут использоваться с инъекцией конструктора IoC.

+0

Что вы думаете, что делает? –

+1

Вы хотите 'Lazy ' для Lazy поведения загрузки. или статический конструктор для кода init, который является общим для всех экземпляров класса. – Aron

+1

Когда вы хотите * начать * инициализировать? О строительстве? –

ответ

5

Ваш код будет работать, но он не является потокобезопасным, _initializeTask можно изменить после проверки его на null и до его инициализации. Это приведет к двум инициализациям. Я бы подумал использовать AsyncLazy<T>, который наследуется от Lazy<T>, который является потокобезопасным.

Тогда при условии LoadAsync возвращается Task, а не Task<T>, ваш код становится (непроверенным):

private AsyncLazy<object> initializeTask = new AsyncLazy<object>(async() => 
      { 
       // Do an action requiring await here 
       await _storageField.LoadAsync(); 
       return null; 
      }); 

public Task InitializeAsync() 
{ 
    return _initializeTask.Value; 
} 

Вы также можете определить необщую версию `AsyncLazy, так что вам не придется возвращать значение из процедуры инициализации.

public class AsyncLazy : Lazy<Task> 
{ 
    public AsyncLazy(Func<Task> taskFactory) : 
     base(() => Task.Run(taskFactory)) { } 
} 

Затем вы можете инициализировать его, используя метод инициализации, однако этот метод должен быть статическим компилятором:

private AsyncLazy _initializeTask = new AsyncLazy(LoadStorageAsync); 

private static async Task LoadStorageAsync() 
{ 
    // Do an action requiring await here 
    await _storageField.LoadAsync(); 
} 

public Task InitializeAsync() 
{ 
    return _initializeTask.Value; 
} 
+0

Оператор с нулевой связью [ссылка] (http://msdn.microsoft.com/en-us/library/ms173224.aspx) не является атомарным между нулевой проверкой и присваиванием? Наверное, это слишком много, на что можно надеяться. Это создает поле с потенциальными побочными эффектами, не так ли? В то время как в основном выполняется тот же самый код, теперь он находится в поле, а не в методе, который уменьшает ясность кода. AsyncLazy действительно похоже на то, что я хочу, но есть ли способ сдерживать его создание в методе, чтобы он был более кратким? –

+1

Я обновил ответ на использование метода, но компилятор потребовал, чтобы метод был статичным. –

+1

Вы можете использовать 'Task.Run (taskFactory)' вместо подхода, используя 'StartNew()' и 'Unwrap()'. – svick

0

Запустить асинхронную задачу из регулярной функции инициализации. В обычной функции проверьте, имеет ли ваше приложение уже загруженный набор данных, соответствующий вашим ожиданиям. Если данных нет, THEN вызовите функцию async.

... 
If (BooleanFunctionToDetermineIfDataIsNotPresent){FunctionToLoadFreshData} 
... 


Private Async Void FunctionToLoadFreshData{...} 

Функция для загрузки данных не должна возвращать значение, чтобы оно не стало самой задачей.