Коллекция потоков и списков <> - PullRequest
0 голосов
/ 25 октября 2011

У меня есть List<string> коллекция под названием List<string> list.

У меня есть две темы.Один поток перечисляет все элементы списка и добавляет в коллекцию.Второй поток перечисляет все элементы списка и удаляет из него.

Как сделать поток безопасным?Я пытался создать глобальный объект "MyLock" и использовать блокировку блокировки (MyLock) в каждой функции потока, но это не сработало.

Можете ли вы помочь мне?

Ответы [ 6 ]

3 голосов
/ 25 октября 2011

Если у вас есть доступ к .NET 4.0, вы можете использовать класс ConcurrentQueue или BlockingCollection с поддержкой ConcurrentQueue.Он делает именно то, что вы пытаетесь сделать, и не требует блокировки.BlockingCollection заставит вашу ветку ждать, если в списке нет доступных элементов.

Пример удаления из ConcurrentQueue вы делаете что-то вроде

ConcurrentQueue<MyClass> cq = new ConcurrentQueue<MyClass>();

void GetStuff()
{
    MyClass item;
    if(cq.TryDeqeue(out item))
    {
        //Work with item
    }
}

Это попытается удалить элемент, но если нет доступных он ничего не делает.

BlockingCollection<MyClass> bc = BlockingCollection<MyClass>(new ConcurrentQueue<MyClass>());

void GetStuff()
{
    if(!bc.IsCompleated) //check to see if CompleatedAdding() was called and the list is empty.
    {
        try
        {
            MyClass item = bc.Take();
            //Work with item
        }
        catch (InvalidOpperationExecption)
        {
            //Take is marked as completed and is empty so there will be nothing to take
        }
    }
}

Это заблокирует и будет ждать на Take, пока не появится что-то, что можно взять из списка.Когда вы закончите, вы можете позвонить CompleteAdding(), и Take выдаст исключение, когда список станет пустым вместо блокировки.

2 голосов
/ 25 октября 2011

Не зная больше о вашей программе и требованиях, я скажу, что это «плохая идея». Изменение List<> при переборе его содержимого, скорее всего, вызовет исключение.

Вам лучше использовать Queue<> вместо List<>, так как Queue<> был разработан с учетом синхронизации.

0 голосов
/ 25 марта 2013

Как уже говорили другие, вы можете использовать одновременные коллекции из пространства имен System.Collections.Concurrent.Если вы можете использовать один из них, это предпочтительнее.

Но если вы действительно хотите список, который только что синхронизирован, вы можете взглянуть на класс SynchronizedCollection<T> в System.Collections.Generic.

* 1007.* Обратите внимание, что вам пришлось включить сборку System.ServiceModel, что также является причиной, по которой мне она не нравится так сильно.Но иногда я им пользуюсь.
0 голосов
/ 25 октября 2011

Вы можете реализовать собственную версию IList<T>, которая обернет базовый List<T>, чтобы обеспечить блокировку при каждом вызове метода.

public class LockingList<T> : IList<T>
{
    public LockingList(IList<T> inner)
    {
        this.Inner = inner;
    }

    private readonly object gate = new object();
    public IList<T> Inner { get; private set; }

    public int IndexOf(T item)
    {
        lock (gate)
        {
            return this.Inner.IndexOf(item);
        }
    }

    public void Insert(int index, T item)
    {
        lock (gate)
        {
            this.Inner.Insert(index, item);
        }
    }

    public void RemoveAt(int index)
    {
        lock (gate)
        {
            this.Inner.RemoveAt(index);
        }
    }

    public T this[int index]
    {
        get
        {
            lock (gate)
            {
                return this.Inner[index];
            }
        }
        set
        {
            lock (gate)
            {
                this.Inner[index] = value;
            }
        }
    }

    public void Add(T item)
    {
        lock (gate)
        {
            this.Inner.Add(item);
        }
    }

    public void Clear()
    {
        lock (gate)
        {
            this.Inner.Clear();
        }
    }

    public bool Contains(T item)
    {
        lock (gate)
        {
            return this.Inner.Contains(item);
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        lock (gate)
        {
            this.Inner.CopyTo(array, arrayIndex);
        }
    }

    public int Count
    {
        get
        {
            lock (gate)
            {
                return this.Inner.Count;
            }
        }
    }

    public bool IsReadOnly
    {
        get
        {
            lock (gate)
            {
                return this.Inner.IsReadOnly;
            }
        }
    }

    public bool Remove(T item)
    {
        lock (gate)
        {
            return this.Inner.Remove(item);
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        lock (gate)
        {
            return this.Inner.ToArray().AsEnumerable().GetEnumerator();
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        lock (gate)
        {
            return this.Inner.ToArray().GetEnumerator();
        }
    }
}

Вы бы использовали этот код так:

var list = new LockingList<int>(new List<int>());

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

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

Использование кода, подобного lock (list) { ... } или lock (list.SyncRoot) { ... } , не защищает вас от изменений списка, происходящих во время перечислений. Эти решения покрывают только одновременные изменения в списке - и это только в том случае, если все вызывающие стороны делают это в пределах блокировки. Кроме того, эти решения могут привести к смерти вашего кода, если какой-то неприятный фрагмент кода захватывает блокировку и не снимает ее.

В моем решении вы заметите, что у меня есть object gate, которая является закрытой переменной внутри класса, который я блокирую. Ничто вне класса не может заблокировать это, так что это безопасно.

Надеюсь, это поможет.

0 голосов
/ 25 октября 2011

Блокировка на SyncRoot вашего List<T>:

lock(list.SyncRoot)
{

}

Более подробную информацию о том, как правильно его использовать, можно найти здесь

0 голосов
/ 25 октября 2011

Вы должны иметь возможность заблокировать непосредственно в своем списке:

lock(list) {
    //work with list here
}

Однако добавление / удаление из списка при перечислении может вызвать исключение ...

...