Как читать файл с чередованием одновременно, используя реактивные расширения

2

Я новичок в реактивных расширениях, и я хотел бы использовать его (в С#) для чтения файла, который содержит несколько потоков, чередующихся. В основном файл находится в формате ABCDABCDABCD.... Я бы предпочел последовательно читать файл и разделять потоки (т.е. AAA.., BBB.. и т.д.) И обрабатывать каждый поток параллельно, используя отдельные потоки для каждого потока.

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

Элементы в файле небольшие (4 байта), поэтому они довольно чаты. Поэтому я также ищу способ эффективно справиться с этим.

Я начал с создания перечислимого для чтения файла. Это может быть сделано для предоставления структуры, содержащей идентификатор потока, или потоки могут быть разделены на основе порядка (номер элемента по модулю количества потоков). Более поздний, вероятно, более эффективен, хотя.

Теги:
system.reactive

2 ответа

3
Лучший ответ

Этот вопрос "зависит" отпечатан на нем, особенно когда вы говорите о производительности и эффективности, но предоставили пример, который несколько надуман. А именно, ваш примерный файл прост по сравнению с реальным файлом. Тем не менее, я попытаюсь дать некоторые советы о том, что это полезно.

Здесь показан способ превращения потока в Enumerable<char>. Поток будет применять буферизацию, это приведет к возврату одного результата за раз. Это можно было бы сделать более эффективным (отправить обратно куски данных), но в какой-то момент вам нужно обрабатывать их по одному, и это может быть и здесь. Не преждевременно оптимизируйте.

IEnumerable<char> ReadBytes(Stream stream)
{
    using (StreamReader reader = new StreamReader(stream))
    {
        while (!reader.EndOfStream)
            yield return (char)reader.Read();
    }
}

Теперь скажем, что это код обработки для "выходных" наблюдаемых. Во-первых, я устанавливаю выходные наблюдаемые вверх, а затем я подписываюсь на них соответствующим образом. Обратите внимание, что я использую массив здесь, поэтому мой выходной наблюдаемый индекс является индексом массива. Можно также использовать словарь, если индекс потока не может быть превращен в индекс с нулевым значением.

var outputs = Enumerable.Repeat(0, 3).Select(_ => new Subject<char>()).ToArray();                                                                                                     

outputs[0].Delay(TimeSpan.FromSeconds(2)).Subscribe(x => Console.WriteLine("hi: {0}", x));
outputs[1].Delay(TimeSpan.FromSeconds(1)).Subscribe(x => Console.WriteLine("ho: {0}", x));
outputs[2].Subscribe(x => Console.WriteLine("he: {0}", x));

Обратите внимание на использование Subject<char> для отправки моих элементов. Это зависит от типа вашего элемента, но char работает в приведенном примере. Обратите также внимание, что я задерживаю элементы только для того, чтобы доказать, что все работает. Теперь они независимые потоки, и вы можете делать с ними все, что хотите.

ОК, учитывая поток файлов:

var file = @"C:\test.txt";
var buffer = 32;
var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, buffer);

Теперь я могу подписаться и использовать индекс modulo для отправки в правый выходной поток:

ReadBytes(stream)
.ToObservable(Scheduler.ThreadPool)
.Select((x,i) => new { Key = (i % 3), Value = x }) // you can change it up here
.Subscribe(x => outputs[x.Key].OnNext(x.Value));

Здесь потенциально более эффективные методы в зависимости от того, как вы можете рассчитать целевой поток, но идея остается прежней.

Входной файл содержит только одну строку: ABCABCABCABCABCABC

Результат запуска программы:

he: C
he: C
he: C
he: C
he: C
he: C

Через секунду:

ho: B
ho: B
ho: B
ho: B
ho: B
ho: B

И затем другая секунда:

hi: A
hi: A
hi: A
hi: A
hi: A
hi: A
  • 0
    Это отличное решение! Однако я намеревался обрабатывать потоки параллельно с несколькими потоками. Я смог сделать это, добавив .ObserveOn(Scheduler.ThreadPool) между outputs[i] и .Subscribe() . Я проверил это со сном внутри .Subscribe() и он, кажется, работает как задумано. Потоки работают параллельно, и для каждого потока он блокируется (поэтому поддерживается порядок элементов для каждого потока). Единственная проблема остается в том, что весь файл читается сразу, я хотел бы добавить максимум к тому, что буферизируется. Так что, если один поток слишком сильно отстает, другие должны голодать.
  • 0
    На самом деле не понимаю, чего вы хотите, если один поток отстает. Это потому, что в файле нет данных для этого потока (в таком случае, что должно произойти)? Или потому, что поток обрабатывает свои элементы дольше?
Показать ещё 3 комментария
1

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

Однако я не уверен, что это правильная реализация (с точки зрения стиля программирования, rx-контрактов и т.д.).

const int MAX_BUFFERED_ELEMENTS = 1024;

// number of streams in the file
var numberOfStreams = 8;

// semaphore to limit buffered elements
var semaphore = new SemaphoreSlim(MAX_BUFFERED_ELEMENTS);
var cts = new CancellationTokenSource(); // should be used to cancel (left out of this sample)

// create subjects that are the base of each output stream
var subjects = Enumerable.Repeat(0, numberOfStreams).Select(_ => new Subject<ElementType>()).ToArray();

// create the source stream (reader is IEnumerable<ElementType>)
var observable = reader.ToObservable(Scheduler.ThreadPool).Publish();

// forward elements from source to the output subjects
int stream = 0;
observable.Subscribe(x => { 
    semaphores.Wait(cts.Token);   // wait if buffer is full
    _subjects[stream].OnNext(x);  // forward to output stream
    if (++stream >= numberOfStreams) stream = 0; }); // stream = stream++ % numberOfStreams

// build output streams
subjects.Select(
    (s,i) => s.ObserveOn(Scheduler.ThreadPool) // process on separate threads
    .Do(_ => semaphore.Release())              // signal that element is consumed
    .Subscribe(x => Console.WriteLine("stream: {0}\t element: {1}", i, x)) // debug 'processing'
    );

// start processing!
observable.Connect();

Ещё вопросы

Сообщество Overcoder
Наверх
Меню