Re[5]: Interlocked
От: RAza  
Дата: 29.12.18 22:18
Оценка:
Здравствуйте, 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;
Отредактировано 29.12.2018 22:25 RAza . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.