Здравствуйте, RushDevion, Вы писали:
RD>Как идея: можно возвращать Task. Тогда проброс ошибок будет автоматическим.
Покрутил идею с Task. Итоговый дизайн публичного API мне не нравится. Хочется сохранить условие что данный тип предоставляет потребителям либо значение, либо исключение.
В итоге опираясь на "академический" пример переписал реализацию добавив обработку ошибок. Существующие тесты она проходит, хотя в ближайшее время количество тест кейсов конечно стоит увеличить. Если у вас будет время и возможность посмотрите на код — сильно опасаюсь что я где то накосячил.
public sealed class MyLazy<T>
{
private volatile Exception error = null;
private volatile Holder holder = null;
private volatile int waiting = 0;
private readonly ManualResetEvent onError = new ManualResetEvent(false);
private readonly Func<T> getAction = null;
private readonly bool canBeReseted;
private int isExist = 0;
public bool HasValue => holder != null;
public T Value
{
get
{
// Предыдущая попытка получить значение вызвала исключение. Все еще имеются ожидающие инициализации потоки. Ждем, пока для каждого будет брошено исключение.
while (error != null)
{
Thread.Sleep(TimeSpan.FromMilliseconds(5));
}
var current = holder;
if (current != null)
return current.Value;
if (Interlocked.CompareExchange(ref isExist, 1, 0) == 0)
{
try
{
// Захватили право на инициализацию, инициируем
holder =
new Holder
{
Value = getAction()
};
}
catch (Exception ex)
{
Interlocked.Exchange(ref isExist, 0);
if (waiting > 0)
{
onError.Set();
error = ex;
}
throw;
}
return holder.Value;
}
Interlocked.Increment(ref waiting);
// Право на инициализацию захватил кто-то другой, ждем, пока инициализация не завершится
while (true)
{
try
{
if (error != null)
throw error;
var result = holder;
if (result != null)
return result.Value;
onError.WaitOne(TimeSpan.FromMilliseconds(5));
}
finally
{
Interlocked.Decrement(ref waiting);
if (waiting == 0)
{
onError.Reset();
error = null;
}
}
}
}
set
{
Interlocked.Exchange(ref isExist, 1);
holder =
new Holder
{
Value = value
};
}
}
public MyLazy(Func<T> getAction, bool canBeReseted)
: this(getAction)
=> this.canBeReseted = canBeReseted;
public MyLazy(Func<T> getAction)
=> this.getAction = getAction;
public void Reset()
{
if (canBeReseted == false)
return;
Interlocked.Exchange(ref isExist, 0);
holder = null;
}
private class Holder
{
public T Value;
}
}
Из появившихся вопросов:
В чем сакральный смысл копирования ссылки на объект с имеющимся значением в локальную переменную? Для текущего потока это значение попадет в его "локальный кеш"?
Речь про вот этот участок кода:
var current = holder;
if (current != null)
return current.Value;