Можно ли положить TryDequeue в цикл while?

Я раньше не использовал параллельную очередь.

Можно ли использовать TryDequeue, как показано ниже, в цикле while? Может ли это не застрять навсегда?

var cq = new ConcurrentQueue<string>();
cq.Enqueue("test");

string retValue;

while(!cq.TryDequeue(out retValue))
{
    // Maybe sleep?
}

//Do rest of code

Ответ 1

Да, это безопасно в соответствии с документацией, но это не рекомендуемый дизайн.

Он может получить "Stuck forever", если очередь была пуста при первом вызове TryDequeue, и если ни один другой поток не подталкивает данные в очереди после этой точки (вы могли бы разбить время после попыток N или после таймаута).

ConcurrentQueue предлагает член IsEmpty для проверки наличия элементов в очереди. Гораздо эффективнее проверять его, чем перебирать вызов TryDequeue (особенно, если очередь вообще пуста)

Что вы можете сделать, это:

while(cq.IsEmpty())
{
    // Maybe sleep / wait / ...
}

if(cq.TryDequeue(out retValue))
{
...
}

EDIT: Если этот последний вызов возвращает false: другой из ваших потоков удаляет объект. Если у вас нет других потоков, это безопасно, если это так, вы должны использовать while (TryDequeue)

Ответ 2

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

Помимо всего этого, у вас есть цикл занятости. Этого практически всегда следует избегать. Либо вы в конечном итоге постоянно проверяете очередь, запрашивая больше предметов, тратя время и время процессора на процесс, или вы заканчиваете спать и, следовательно, фактически не используете элемент в очереди, как только он будет добавлен (и даже тогда, все еще теряя смысл некоторое время/усилие на контекст переключает только для опроса очереди).

Что вы должны делать вместо этого, если вы окажетесь в положении, когда вам нужно "подождать, пока не появится элемент для меня", используйте BlockingCollection. Он специально предназначен для переноса различных типов параллельных коллекций и блокировки до тех пор, пока не будет доступный элемент. Это позволяет вам изменить свой код на queue.Take(), и будет проще писать, семантически заявляя, что вы делаете, быть четко правильным, заметно более эффективным и полностью безопасным.