#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 #.