Как заблокировать коллекцию на время запроса LINQ? - PullRequest
1 голос
/ 07 июля 2011

У меня есть класс репозитория, который содержит потенциально большое количество элементов в памяти. Я хочу иметь возможность запрашивать его, используя LINQ-to-objects; подвох в том, что репозиторий должен быть потокобезопасным - ему необходимо заблокировать внутреннюю коллекцию на время запроса. Лучший синтаксис, который мне удалось придумать, продемонстрирован в примере кода ниже.

class Program
{
    class Repository<T>
    {
        private List<T> _items;

        public Repository(IEnumerable<T> items)
        {
            _items = items.ToList();
        }

        public List<TResult> Query<TResult>(Func<IEnumerable<T>, IEnumerable<TResult>> queryBuilder)
        {
            lock (_items)
            {
                var query = queryBuilder(_items);
                return query.ToList();
            }
        }
    }

    static void Main(string[] args)
    {
        var repo = new Repository<int>(new[] { 1, 2, 4, 8, 16, 32 });
        var result = repo.Query(r => 
                            from i in r
                            where i > 4
                            orderby i descending
                            select i.ToString());

        foreach (var i in result)
            Console.WriteLine(i);

        Console.Read();
    }
}

Приведенный выше код работает и гарантирует, что блокировка удерживается только столько времени, сколько требуется для выполнения запроса. Однако в идеале я хотел бы использовать более естественную парадигму LINQ и предоставить свойство типа IEnumerable {T} или IQueryable {T} из класса Repository, например, так:

    static void Main(string[] args)
    {
        var repo = new Repository<int>(new[] { 1, 2, 4, 8, 16, 32 });
        var result = from i in repo.Items
                 where i > 4
                 orderby i descending
                 select i);

        foreach (var i in result)
            Console.WriteLine(i);

        Console.Read();
    }

Я не могу понять, как это сделать, сохранив семантику блокировки исходного кода. Возможно ли это?

Ответы [ 2 ]

1 голос
/ 17 июля 2011

В случае, если кому-то будет интересно, решение, которое я в конечном итоге придумал, состояло в том, чтобы создать обзорную конструкцию для управления временем жизни блокировки с помощью блока «using» ... вызывающий код в конечном итоге выглядел примерно так:

static void Main(string[] args)
{
    var repo = new Repository<int>(new[] { 1, 2, 4, 8, 16, 32 });


    using(var queryScope = repo.CreateQueryScope())
    {
        var result = from i in queryScope.Items
             where i > 4
             orderby i descending
             select i;

        foreach (var i in result)
            Console.WriteLine(i);
    }

    Console.Read();
}

Это решение предоставляет семантику, аналогичную той, которая была бы у DataContext LINQ-to-sql ... Блок using предоставляет вызывающей стороне четкий контракт о том, что коллекция, на которой работает запрос, будет заблокирована только длядлительность запроса.Хотя это не мешает вызывающей стороне делать что-то глупое, например, перечислять запрос за пределами блока using, это дает четкую подсказку, чтобы обескуражить это тем фактом, что коллекция предоставляется через QueryScope, а не в самом классе репозитория.

0 голосов
/ 14 июля 2011

Вот идея:

public IEnumerable<T> Items
{
    get 
    {
        lock(_items)
        {
            foreach(T item in _items)
                yield return item;
        }
    }
}

Однако делать что-то подобное может быть опасно. Видите ли, IEnumerable не означает, что сет когда-либо истощается. Например, что если я позвоню repo.first(), то foreach никогда не вернется и блокировка не будет снята. Поэтому, если я снова позвоню repo.first(), он полностью блокируется.

Я думал, что пользовательский перечислитель сделает что-то подобное, но вы столкнулись с той же проблемой.

Я думаю, что ваш путь, вероятно, лучше.

...