Как стать автором
Обновить

Комментарии 18

Я бы все-таки отказался от new TItemQueue() в сторону защищенного конструктора. Просто потому что может понадобиться передать в конструктор внутренней коллекции несколько параметров. Тот факт, что с внутренней коллекцией теоретически смогут работать без нашего ведома — не страшен, надо просто запретить такую работу в документации, а кто так сделает — тот сам себе злобный буратино. В конце концов, это можно будет сделать только тому, кто явно наследуется от AsyncCollection — а наследуясь от библиотечного класса надо всегда понимать, что ты делаешь.

Вместо TaskCompletionSource в _awaiterQueue лучше хранить обобщенный делегат вида bool ItemConsumer<TItem>(TItem value). Это позволит очень просто реализовать операцию TakeFromAny.
Я бы все-таки отказался от new TItemQueue() в сторону защищенного конструктора.

Мысль про protected конструктор интересная. Явно проще и элегантнее, чем для передачи пачки параметров писать враппер, реализующий IProducerConsumerCollection. Я подумаю ;)

Вместо TaskCompletionSource в _awaiterQueue лучше хранить обобщенный делегат вида bool ItemConsumer<TItem&rt;(TItem value). Это позволит очень просто реализовать операцию TakeFromAny.

Я думаю в направлении interface IConsumer с похожим методом — навскидку кажется, что так реализация TakeAnyAsync() получится немного чище. Но пока не берусь утверждать как будет на самом деле, идея пока на стадии обкатывания в голове.
На самом деле получилось не очень хорошо — к сожалению, нельзя так просто взять и отменить поглощение элемента, когда очередь наполнена (элементы доминируют). В итоге, не обошлось без небольшой блокировки.

Но зато то, что получилось — работает.
Очевидно, что автору требовалась просто асинхронная коллекция — а не целый фреймворк.
TPL Dataflow никак не подходит под определение «фреймворк». В контексте обсуждаемой задачи его надо применять как типичную библиотеку.
Ну, если достать оттуда один только BufferBlock — то да. Кстати, надо будет проверить производительность…
HellBrick.AsyncCollections.AsyncQueue       :  13ms
Nito.AsyncEx.AsyncCollection                : 363ms
System.Concurrent.BlockingCollection        :  23ms
System.Threading.Tasks.Dataflow.BufferBlock :  22ms
Не быстрее BlockingCollection — возможно, внутри она и есть.
Производительность у библиотеки Dataflow может не рекордная (но вполне приличная). Но она позволяет строить произвольные цепочки обработки данных. Например, как в обсуждаемой статье, очередь с обработкой пачками с помощью BatchBlock.
Он не то чтобы принципиально не угодил, я с интересом к нему присматриваюсь. Но пока сложилось впечатление, что оптимальное использование DataFlow — выстроить на нём весь пайплайн обработки даннных, что немного оверкилл для моих целей.
Я, конечно же, ничего не знаю про всю вашу систему в целом. Но, мне кажется, излишеством и перебором является написание велосипедов, подобных обсуждаемому. А библиотека Dataflow довольна проста. После понимания нескольких базовых классов, её внедрение обычно сводится к простому вырезанию ненужных кусков вашего кода.
Вполне возможно, что вы правы и in the long run переписать всё на DataFlow было бы оптимальнее. С другой стороны, в процессе пришлось узнать много интересного про мемори барьеры и иже с ними, так что как минимум академический смысл во всей этой затее присутствует.

Пользуясь случаем: а в DataFlow есть аналог очереди с приоритетами? Беглый просмотр неймспейса на MSDN результата не дал.
Вы начали изобретать велосипед lock-free коллекции, или раньше имели дело с ними и просто решили реализовать на .NET?
Если lock-free и wait-free для Вас незнакомое слово, то настоятельно рекомендую ознакомиться с различными статьями (есть даже на Хабре) на эту тему — Вам понравится!
я так понял, что требовалось не убрать ожидание и блокировки (которые неизбежны при получении асинхронно поступающих элементов), а сделать ожидание асинхронным
Конечным итогом видится сборка lock-free и wait-free коллекций для .NET как libcds для C++.
Такие коллекции уже есть. ConcurrentQueue, к примеру. Задача автора была совсем в другом.
НЛО прилетело и опубликовало эту надпись здесь
Дочитав до строк
Если происходит вызов Add(), у нас появляется новый элемент, с помощью которого можно мгновенно взять один из awaiter-ов и завершить его.

сразу вспомнил AsyncProducerConsumerCollection из небольшой книги от Stephen Toub Task-based Asynchronous Pattern. Кстати, ссылку на эту книгу я нашёл у того же Stephen Cleary в его статье 2012 года. Странно почему Stephen Cleary не реализовал подобные быстрые асинхронные коллекции в Nito.AsyncEx.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.