Удаление AsyncLazy, каков правильный (простой в использовании и негерметичный) способ?

#c# #async-await

#c# #асинхронное ожидание

Вопрос:

Я использую специализацию реализации AsyncLazy Стивена Клири из его блога.

 /// <summary>
/// Provides support for asynchronous lazy initialization.
/// This type is fully thread-safe.
/// </summary>
/// <typeparam name="T">
/// The type of object that is being asynchronously initialized.
/// </typeparam>
public sealed class AsyncLazy<T>
{
    /// <summary>
    /// The underlying lazy task.
    /// </summary>
    private readonly Lazy<Task<T>> instance;

    /// <summary>
    /// Initializes a new instance of the 
    /// <see cref="AsyncLazyamp;<Tamp;>"/> class.
    /// </summary>
    /// <param name="factory">
    /// The delegate that is invoked on a background thread to produce
    /// the value when it is needed.
    /// </param>
    /// <param name="start">
    /// If <c>true</c> commence initialization immediately.
    /// </param>
    public AsyncLazy(Func<T> factory, bool start = false)
    {
        this.instance = new Lazy<Task<T>>(() => Task.Run(factory));
        if (start)
        {
            this.Start();
        }
    }

    /// <summary>
    /// Initializes a new instance of the 
    /// <see cref="AsyncLazyamp;<Tamp;>"/> class.
    /// </summary>
    /// <param name="factory">
    /// The asynchronous delegate that is invoked on a background 
    /// thread to produce the value when it is needed.
    /// </param>
    /// <param name="start">
    /// If <c>true</c> commence initialization immediately.
    /// </param>
    public AsyncLazy(Func<Task<T>> factory, bool start = false)
    {
        this.instance = new Lazy<Task<T>>(() => Task.Run(factory));
        if (start)
        {
            this.Start();
        }
    }

    /// <summary>
    /// Asynchronous infrastructure support.
    /// This method permits instances of
    /// <see cref="AsyncLazyamp;<Tamp;>"/> to be await'ed.
    /// </summary>
    public TaskAwaiter<T> GetAwaiter()
    {
        return this.instance.Value.GetAwaiter();
    }

    /// <summary>
    ///     Starts the asynchronous initialization, 
    ///     if it has not already started.
    /// </summary>
    public void Start()
    {
        var unused = this.instance.Value;
    }
}
  

Это отличный код, и я действительно ценю, насколько он прост в использовании. т.Е.

 class SomeClass
{
    private readonly AsyncLazy<Thing> theThing = new AsyncLazy<Thing>(
        () => new Thing());

    void SomeMethod()
    {
        var thing = await theThing;
        // ...
    }
}
  

Теперь мой вопрос,

Предположим, что SomeClass наследуется от класса, который реализует IDisposable , и который Thing реализует IDisposable . У нас была бы скелетная реализация, подобная этой,

 class SomeClass : SomeDisposableBase
{
    private readonly AsyncLazy<Thing> theThing = new AsyncLazy<Thing>(
        () => new Thing());

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // What do I do with theThing?
        }

        base.Dispose(disposing);
    }
}
  

Итак, что мне делать с theThing в Dispose переопределении? Должен ли я расширить AsyncLazy<T> , чтобы иметь новое свойство?

 // ...

public bool IsStarted
{
    get
    {
        return this.instance.IsValueCreated;
    }
}

// ...
  

Должен ли я изменить, AsyncLazy<T> чтобы реализовать IDisposable ?

Я неправильно понял, и мне не нужно беспокоиться?

Должен ли я сделать что-то еще?

Комментарии:

1. На моем месте я бы выбрал последний вариант… расширьте AsyncLazy такое значение для реализации IDisposable , затем, если someT as IDisposable значение не равно нулю, удалите его.

2. @spender, я испытываю искушение, но потом я считаю, что T не всегда буду это реализовывать IDisposable , так что это своего рода расточительство. Кроме того, Lazy<T> не реализуется IDisposable , поэтому я бы, так сказать, нарушил шаблон.

3. @spender, конечно, выполнение всех проверок, ожидание и удаление для каждого экземпляра — это боль.

4. Есть встроенная AsyncLazy в сборку Microsoft.VisualStudio.Threading , посмотрите здесь

Ответ №1:

Версия этого класса Стивена Тауба наследуется от Lazy<Task<T>> , поэтому вы получаете IsValueCreated свойство автоматически.

В качестве альтернативы вы могли бы предоставить IsValueCreated свойство из частного поля:

 public sealed class AsyncLazy<T>
{
    private readonly Lazy<Task<T>> instance;
    ...
    public bool IsValueCreated
    {
        get { return instance.IsValueCreated; }
    }
}
  

Для согласованности со встроенным Lazy<T> типом я бы не стал переименовывать свойство в IsStarted .

Комментарии:

1. Это то, что я, вероятно, сделаю, но я думаю, что семантически правильно предоставлять свойство как IsStarted поскольку создание задачи не обязательно означает, что значение создается.

Ответ №2:

Вы можете использовать bool внутри AsyncLazy<T> инициализации, чтобы узнать, была ли theThing инициализирована

 class SomeClass : SomeDisposableBase
{
   public SomeClass()
   {
      theThing = new AsyncLazy<Thing>(() => 
      { 
         _isInitialized = true;
         return new Thing();
      } 
   }

   private bool _isInitialized;
   private readonly AsyncLazy<Thing> theThing;

protected override void Dispose(bool disposing)
{
    if (disposing amp;amp; _isInitialized)
    {
        // Dispose Thing
    }

    base.Dispose(disposing);
 }
}
  

Хотя, если этот шаблон встречается в вашем коде более одного раза, тогда я бы определенно расширил AsyncLazy

Комментарии:

1. у моего AsyncLazy<T> пока IsValueCreated нет.

2. Я думал, что реализация Стефана наследуется IsValueCreated . Вместо этого вы можете использовать простой bool . Я исправлю свой код.

3. Какой Стивен? Тоб да, явно нет.

4. Я модифицировал код для работы с версией, которую вы опубликовали, от Стефана Клири. Слишком много стефанов, делающих одно и то же 🙂

5. Этот лямбда-синтаксис интересен, хотя это, конечно, не C #.