Как контролировать доступ нескольких потоков к коллекции объектов?

Я пишу приложение, которое отображает список объектов, которые пользователь может выбирать, а затем просматривать и редактировать свойства с помощью элемента управления PropertyGrid. Свойства объекта заполняются трудоемким процессом извлечения информации из файлов с использованием вторичного потока. Но я также хотел бы позволить пользователю продолжать просматривать другие объекты по мере выполнения процесса извлечения.

После прочтения ответов на мои предыдущие вопросы по SO. Похоже, что поскольку свойства, которые записываются в процессе извлечения, не пересекаются со свойствами, доступными для редактирования пользователем через сетку свойств, у меня не должно быть проблем с двумя потоками, редактирующими объекты одновременно. Хотя пользователь может увидеть неправильное значение, если ему невероятно не повезло, и сетка свойств в конечном итоге считывает объект в середине неатомарной записи.

Тем не менее, я все еще хотел бы знать, как я могу настроить это, чтобы пользователь не мог редактировать или просматривать объект, который находится в процессе извлечения. Я очень-очень новичок в многопоточности, но большинство примеров, которые я читал, показывают, что создается отдельный токен-объект, который используется для блокировки доступа к фактическому интересующему объекту. Ответы на мой другой предыдущий вопрос подтвердили, что создание отдельный объект, подобный этому, специально для блокировки.

Итак, теперь я хотел бы знать, как это обрабатывается в моем случае, когда у меня есть большая коллекция объектов? Я хотел бы создать блокировки, которые не позволяют сетке свойств отображать объект, который выбирает пользователь, если он в настоящее время извлекается.

Нужно ли мне создавать отдельную коллекцию объектов блокировки, которая синхронизируется с моей реальной коллекцией? Итак, если объект добавляется или удаляется из моей основной коллекции, мне нужно добавлять или удалять объекты блокировки из моей коллекции блокировок?

Должен ли я блокировать фактические объекты, а не создавать отдельные объекты блокировки токена?

Как насчет добавления логического свойства «IsBeingExtracted» к объектам, которые сетка свойств может проверять, находится ли объект в процессе записи? Затем это будет установлено в самом начале и в самом конце процесса извлечения.

Или как насчет статического поля где-то, которое ссылается на текущий (если есть) объект, который в настоящее время извлекается. Затем сетка свойств может проверить, что последний объект, который был запрошен для отображения, не был объектом, на который ссылается это статическое поле? Это, конечно, не сработало бы, если бы было несколько потоков извлечения.

Как лучше/правильнее это сделать? Лично мне больше всего нравится вариант логического свойства, но я хотел бы знать, что думают другие, которые на самом деле знают, что они делают.


person Eric Anastas    schedule 26.02.2010    source источник


Ответы (4)


Не могли бы вы просто сделать коллекцию, содержащую объекты, SynchronizedCollection<T>? Он гарантирует, что только один поток может одновременно добавлять или получать доступ к объектам. Затем, вместо того чтобы беспокоиться о том, как синхронизировать доступ к свойствам каждого объекта, не добавляйте объект в коллекцию, пока она не будет заполнена.

Что-то вроде этого:

private readonly ICollection<Item> Items = new SynchronizedCollection<Item>();

// Run this on the background thread.
public void PopulateItems()
{
    using (var file = File.OpenRead("BigFile.txt"))
    using (var reader = new StreamReader(file))
    {
        while (!reader.EndOfStream)
        {
            var item = new Item();
            PopulateItem(item);
            Items.Add(item);
        }
    }
}

public void PopulateItem(Item item)
{
    // Do time-consuming work.
}

Сетка свойств может с радостью делать с объектом все, что захочет, потому что к тому времени, когда он появляется в списке, поток извлечения завершает работу с ним. Не требуется явная блокировка.

person Rory MacLeod    schedule 27.02.2010

Сделайте свою коллекцию объектов словарем, а затем заблокируйте ключи.

person Payton Byrd    schedule 26.02.2010

В общем, использование одной или нескольких блокировок зависит от степени параллелизма, которого вы хотите достичь.

Чем меньше у вас блокировок (скажем, одна глобальная блокировка), тем меньше параллелизма будет, потому что многие операции могут конкурировать за блокировку и препятствовать выполнению друг друга.

Чем больше у вас блокировок, тем большего параллелизма вы можете достичь, но тем больше затрат на управление блокировками.

Использование блокировки, которая является изменяемым объектом, имеет смысл, если то, что вы защищаете с помощью блокировки, является только этим объектом. Вы увидите много примеров использования отдельного объекта, потому что тогда вам не нужно думать об объеме защиты.

В вашем конкретном случае я не очень понимаю, что защищается. Вы пытаетесь предотвратить одновременную модификацию свойства объекта, то есть фоновым процессом или пользователем? Поскольку пользователь не может делать более одного действия одновременно, нет необходимости в большом количестве параллелизма, поэтому нет необходимости иметь много блокировок. Что вы могли бы сделать, так это иметь единую блокировку, которая берется, когда сетка свойств входит в режим редактирования и когда фоновый процесс собирается установить то же самое редактируемое свойство (в противном случае нет необходимости в блокировках).

person Timores    schedule 26.02.2010

Я думаю, что лучше всего было бы использовать объект ManualResetEvent с тайм-аутом. Таким образом, вы можете сообщить пользователю, что он извлекается, и повторить попытку через несколько секунд.

public class Item : IDisposable // I know it is a horrible class name...
{
    private readonly ManualResetEvent accessibleEvent = new ManualResetEvent(false);

    public void Extract()
    {
        try
        {
            // .....
        }
        finally
        {
            accessibleEvent.Set(); // unlock             
        }
    }

    public void Edit()
    {
        if (!accessibleEvent.WaitOne(1000)) // wait 1 second
        {
            // notify user?    
        }

        // ....
    }

    public void Dispose()
    {
        ((IDisposable)accessibleEvent).Dispose();
    }
}
person ChaosPandion    schedule 26.02.2010
comment
Почему блокировку приходится удерживать секунду или больше? Частью синхронизации потоков является минимизация продолжительности времени, в течение которого они могут конфликтовать. - person Rory MacLeod; 27.02.2010
comment
@Rory - это время ожидания редактирования элемента, а не время удержания блокировки. - person ChaosPandion; 27.02.2010