Еще один вопрос Rx сегодня :)
Проще говоря, я использую асинхронный ввод-вывод для работы с Streams. Однако, как мы все знаем, мы не обязательно получаем все нужные байты при использовании асинхронного чтения - следовательно, возвращаем XAsync
int по методам XAsync
. Мне было интересно, как я могу сказать Rx Observable
чтобы повторить чтение, которое не читало правильное количество байтов из потока и смещено на правильную сумму?
В настоящее время у меня есть это, но не знаю, как установить параметр offset в ReadAsync.
private IDisposable _streamMessageContract;
private readonly byte[] _readBuffer = new byte[8024];
public void Start()
{
// Subscribe to the stream for dataz
_streamMessageContract = Observable.FromAsync<int>(() => _stream.ReadAsync(_readBuffer, 0, _readBuffer.Length))
.Repeat()
.Subscribe(
y => _RawBytesReceived(_readBuffer, y),
ex => _Exception(ex),
() => _StreamClosed());
}
#region Helpers
private void _RawBytesReceived(byte[] bytes, int actualBytesRead)
{
}
private void _StreamClosed()
{
}
private void _Exception(Exception e)
{
}
#endregion
Самый простой способ - просто использовать локальную переменную в закрытии, плюс Defer
чтобы заставить наблюдаемую переоценивать свою функцию на каждой итерации.
Предполагая, что вы хотите возобновить чтение следующего блока после завершения текущего блока, вы получите что-то вроде этого...
// An observable that will yield the next block in the stream.
// If the resulting block length is less than blockSize, then the
// end of the stream was reached.
private IObservable<byte[]> ReadBlock(Stream stream, int blockSize)
{
// Wrap the whole thing in Defer() so that we
// get a new memory block each time this observable is subscribed.
return Observable.Defer(() =>
{
var block = new byte[blockSize];
int numRead = 0;
return Observable
.Defer(() => Observable.FromAsync<int>(() =>
stream.ReadAsync(block, numRead, block.Length - numRead)))
.Do(y -=> numRead += y)
.Repeat()
.TakeWhile(y => y > 0 && numRead != blockSize) // take until EOF or entire block is read
.LastOrDefaultAsync() // only emit the final result of this operation
.Select(_ =>
{
// If we hit EOF, then resize our result array to
// the number of bytes actually read
if (numRead < blockSize)
{
block = block.Take(numRead).ToArray();
}
return block;
});
});
}
public void Start()
{
// Subscribe to the stream for dataz.
// Just keep reading blocks until
// we get the final (under-sized) block
_streamMessageContract = ReadBlock(stream, 8024)
.Repeat()
.TakeWhile(block => block.Length == 8024) // if block is small then that means we hit the end of the stream
.Subscribe(
block => _RawBytesReceived(block),
ex => _Exception(ex),
() => _StreamClosed());
}
ReadBlock
с размером блока 4 или любым другим размером префикса. Затем используйтеSelectMany
для повторного вызоваReadBlock
с длиной префикса, которую вы только что прочитали. Что-то вродеReadBlock(stream, 4).SelectMany(p => p == null ? Observable.Return<byte[]>(null) : ReadBlock(stream, BitConverter.ToInt32(p, 0)).Repeat().TakeWhile(message => message != null)
и изменить ReadBlock испускатьnull
, когда он встречает EOF вместо испускать массив меньшего размера.