S>>>А тут rc не будет случаем? Может continuation прицепить к родителю (task)? RD>>Так оно и цепляется к task RD>>Я возможностей для rc не вижу.
S>Пользователь может воспользовать знач. task, до того как оно сброситься в null.
Так это нормально. Последовательность примерно такая:
1. Пришел поток1. Создал таску инициализации. Ждет ее результата.
2. Пришел поток2. Ему отдали готовую таску, созданную потоком1.
3. Таска зафейлилась. Каждый из потоков получил свой Exception. На первом потоке запустился continuation (ExecuteSynchronously), который должен сбросить task в null
4. В этот момент пришел поток3.
Для него возможны два сценария:
4.1. Поток1 еще не успел сбросить task в null. В этом случае поток3 получит тот же самый task, что потоки1/2 и сразу зафейлится (ну не повезло, че).
4.2. Поток1 успел сбросить task. В этом случае поток3 запустит таску инициализации по-новой.
S>Уточню, что прицепив его AttachToParent, task не будет считаться законченным, пока не закончаться его потомки. Как-то так...(могу ошибаться)
Да, пожалуй, есть смысл добавить и AttachedToParent.
Если потоки получают Value в цикле это исключит вероятность 4.1 для потока2 при повторной попытке получения Value.
Здравствуйте, GlebZ, Вы писали:
GZ>interlocked — это указание компилятору о защищенной команде, что выливается на уровне ассемблера в генерацию префикса lock для текущей команды, например при инкрементации значения с возвратом lock xadd xxx,xxx Чрезвычайно быстрая хрень.
Что за дичь написана? Это атомарные инструкции процессора (буквально одна инструкция процессора), а не генерация префиска и т.д.
Здравствуйте, Sharov, Вы писали:
GZ>>interlocked — это указание компилятору о защищенной команде, что выливается на уровне ассемблера в генерацию префикса lock для текущей команды, например при инкрементации значения с возвратом lock xadd xxx,xxx Чрезвычайно быстрая хрень.
S>Что за дичь написана? Это атомарные инструкции процессора (буквально одна инструкция процессора), а не генерация префиска и т.д.
Не понял? Это команда компилятору на генерацию префикса lock для текущей команды, и эта команда будет выполнена атомарно при взведенном флаге процессора lock. Что тут непонятного?
Здравствуйте, GlebZ, Вы писали:
S>>Что за дичь написана? Это атомарные инструкции процессора (буквально одна инструкция процессора), а не генерация префиска и т.д. GZ>Не понял? Это команда компилятору на генерацию префикса lock для текущей команды, и эта команда будет выполнена атомарно при взведенном флаге процессора lock. Что тут непонятного?
За чем что-то генерировать, когда есть конкретне инсрукции процессора? Это для msil, где про енто можно почитать? Иначе это будет похоже на транзакционную память(или вроде ентого). Т.е. так
можно и FFT атомарно делать.
Еще раз, можно ссылку на генерацию lock и т.д. ибо я себе это нескольок иначе представлял. Вомзожно я неправ.
Здравствуйте, Sharov, Вы писали:
S>За чем что-то генерировать, когда есть конкретне инсрукции процессора? Это для msil, где про енто можно почитать? Иначе это будет похоже на транзакционную память(или вроде ентого). Т.е. так S>можно и FFT атомарно делать.
S>Еще раз, можно ссылку на генерацию lock и т.д. ибо я себе это нескольок иначе представлял. Вомзожно я неправ.
Первая ссылка из гугла http://www.club155.ru/x86cmd/LOCK
Здравствуйте, 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;