如何处理ac#流中的位置
流上的 position
属性的(整个)文档说:
- 在派生类中重写时,获取或设置当前流中的位置。
- Position 属性不会跟踪流中已消耗、跳过或两者的字节数。
就是这样。 好的,我们很清楚它没有告诉我们什么,但我真的很想知道它实际上代表什么。 “位置”是什么??我们为什么要修改或阅读它?如果我们改变它——会发生什么?
在一个实际的示例中,我有一个定期写入的流,并且有一个尝试从中读取数据的线程(最好尽快)。 通过阅读许多 SO 问题,我将 position
字段重置为零以开始阅读。完成此操作后:
- 这是否会影响该流的写入者尝试将数据放入的位置?我需要自己跟踪最后的写入位置吗? (即,如果我将位置设置为零来读取,写入器是否开始覆盖第一个字节中的所有内容?)
- 如果是这样,由于我的原因,我是否需要围绕此“位置”字段使用信号量/锁(也许是子类化?)两个线程访问它?
- 如果我不处理这个属性,作者是否会溢出缓冲区?
也许我不理解 Stream 本身 - 我将它视为一个 FIFO 管道:将数据推入一端,并在另一端将其吸出。 如果不是这样,那么我是否必须继续将上次读取后的数据(即从位置 0x84 开始)复制回缓冲区的开头?
我认真地尝试研究所有这些已经有一段时间了 - 但我是 .NET 的新手。也许溪流有着悠久、令人自豪的(未记录的)历史,而其他人都隐晦地理解这一点。但对于新手来说,这就像阅读你的汽车手册,然后发现:
加速踏板会影响输送到喷油器的燃油和空气量。它不会影响娱乐系统的音量或任何轮胎(如果安装)中的气压。
从技术上讲是正确的,但严肃地说,我们想知道的是,如果我们将其捣碎到地板上,您的速度会更快..
编辑 - 更大的图片
我的数据来自以下任一来源:一个串行端口、一个套接字或一个文件,并有一个线程等待新数据,并将其写入一个或多个流 - 全部相同。
我可以从另一台电脑的 telnet 会话访问其中一个流,并且一切正常。
我现在遇到的问题是在同一程序中(在另一个重复的流上)解析代码中的数据。我将数据复制到 MemoryStream,并有一个线程来解密数据,并将其传递回 UI。 该线程在其自己的缓冲区中执行 dataStream.BeginRead()
操作,返回一些(?)数据量,最多但不超过 count
参数。在处理完从 BeginRead
返回的所有内容后,我将剩余的数据(从读取点的末尾到流的末尾)复制到缓冲区的开头,这样它就赢了不会溢出。
此时,由于写入和读取都是异步的,我不知道是否可以更改位置(因为它是“光标” - 感谢乔恩)。即使向另一个线程发送一条消息说我刚刚读取了 28 个字节,或者其他什么 - 它不会知道它们是哪个 28 个字节,并且不知道如何重置它光标/位置
。 我没有对任何流进行子类化 - 我刚刚创建了一个 MemoryStream,并将其传递给将数据复制到任何需要的流的线程。
这一切都感觉太复杂,不可能是正确的做法 - 我只是找不到一个可以根据需要修改的简单示例。
人们还如何处理长期零星数据需要发送到其他不能立即执行的任务的流?
编辑:可能的解决方案
由于答案中的信息,尝试在队列周围编写流包装器时,我偶然发现这篇文章由 Stephen Toub 撰写。
他写了一个BlockingStream
,并解释道:
.NET Framework 中的大多数流都不是线程安全的,这意味着多个线程无法同时安全地访问流的实例,并且大多数流维护下一次读取或写入将发生的单个位置。另一方面,BlockingStream 是线程安全的,并且从某种意义上说,它隐式维护两个位置,尽管两个位置都没有作为数值公开给该类型的用户。 BlockingStream 的工作原理是维护写入其中的数据缓冲区的内部队列。当数据写入流时,写入的缓冲区将被排队。当从流中读取数据时,缓冲区会按照先进先出 (FIFO) 的顺序出列,并将其中的数据交还给调用者。从这个意义上说,流中存在下一次写入将发生的位置和下一次读取将发生的位置。
这似乎正是我正在寻找的东西 - 所以感谢回答者,我只是从你们的答案中找到了这个。
The (entire) documentation for the position
property on a stream says:
- When overridden in a derived class, gets or sets the position within the current stream.
- The Position property does not keep track of the number of bytes from the stream that have been consumed, skipped, or both.
That's it.
OK, so we're fairly clear on what it doesn't tell us, but I'd really like to know what it in fact does stand for. What is 'the position' for? Why would we want to alter or read it? If we change it - what happens?
In a pratical example, I have a a stream that periodically gets written to, and I have a thread that attempts to read from it (ideally ASAP).
From reading many SO issues, I reset the position
field to zero to start my reading. Once this is done:
- Does this affect where the writer to this stream is going to attempt to put the data? Do I need to keep track of the last write position myself? (ie if I set the position to zero to read, does the writer begin to overwrite everything from the first byte?)
- If so, do I need a semaphore/lock around this 'position' field (subclassing, perhaps?) due to my two threads accessing it?
- If I don't handle this property, does the writer just overflow the buffer?
Perhaps I don't understand the Stream itself - I'm regarding it as a FIFO pipe: shove data in at one end, and suck it out at the other.
If it's not like this, then do I have to keep copying the data past my last read (ie from position 0x84 on) back to the start of my buffer?
I've seriously tried to research all of this for quite some time - but I'm new to .NET. Perhaps the Streams have a long, proud (undocumented) history that everyone else implicitly understands. But for a newcomer, it's like reading the manual to your car, and finding out:
The accelerator pedal affects the volume of fuel and air sent to the fuel injectors. It does not affect the volume of the entertainment system, or the air pressure in any of the tires, if fitted.
Technically true, but seriously, what we want to know is that if we mash it to the floor you go faster..
EDIT - Bigger Picture
I have data coming in either from a serial port, a socket, or a file, and have a thread that sits there waiting for new data, and writing it to one or more streams - all identical.
One of these streams I can access from a telnet session from another pc, and that all works fine.
The problem I'm having now is parsing the data in code in the same program (on another of the duplicated streams). I'm duplicating the data to a MemoryStream, and have a thread to sit and decipher the data, and pass it back up to the UI.
This thread does a dataStream.BeginRead()
into it's own buffer, which returns some(?) amount of data up to but not more than the count
argument. After I've dealt with whatever I got back from the BeginRead
, I copy the remaining data (from the end of my read point to the end of the stream) to the start of my buffer so it won't overflow.
At this point, since both the writing and reading are asynchronous, I don't know if I can change the position (since it's a 'cursor' - thanks Jon). Even if send a message to the other thread to say that I've just read 28 bytes, or whatever - it won't know which 28 bytes they were, and won't know how to reset it's cursor/position
.
I haven't subclassed any streams - I've just created a MemoryStream, and passed that to the thread that duplicates the data out to whatever streams are needed.
This all feels too complex to be the right way of doing it - I'm just unable to find a simple example I can modify as needed..
How else do people deal with a long-term sporadic data stream that needs to be send to some other task that isn't instantaneous to perform?
EDIT: Probable Solution
While trying to write a Stream wrapper around a queue due to information in the answers, I stumbled upon this post by Stephen Toub.
He has written a BlockingStream
, and explains:
Most streams in the .NET Framework are not thread safe, meaning that multiple threads can't safely access an instance of the stream concurrently and most streams maintain a single position at which the next read or write will occur. BlockingStream, on the other hand, is thread safe, and, in a sense, it implicitly maintains two positions, though neither is exposed as a numerical value to the user of the type.
BlockingStream works by maintaining an internal queue of data buffers written to it. When data is written to the stream, the buffer written is enqueued. When data is read from the stream, a buffer is dequeued in a first-in-first-out (FIFO) order, and the data in it is handed back to the caller. In that sense, there is a position in the stream at which the next write will occur and a position at which the next read will occur.
This seems exactly what I was looking for - so thanks for the answerrs guys, I only found this from your answers.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为您对文档的期望有点过高。它确实告诉您一切的具体用途,但并没有告诉您如何使用它。如果您不熟悉流,仅阅读文档不会为您提供足够的信息来实际了解如何使用它们。
我们看看文档是怎么说的:
这是“标准文档说法”,表示该属性旨在跟踪流中的位置,但
Stream
类本身不提供该属性的实际实现实现位于从Stream
类派生的类中,例如FileStream
或MemoryStream
每个都有自己的维护位置的系统。 ,因为它们针对完全不同的后端工作,甚至可能存在
Position
属性没有意义的流实现,您可以使用CanSeek
属性来找出答案。流实现是否支持位置。这意味着
Position
属性表示后端实现中的绝对位置,它不仅仅是已读取或写入内容的计数器。读取和写入流的方法使用跟踪读取或写入位置的位置,对于不支持位置的流实现来说,它仍然可以返回已读取或写入的字节数,但它不会。
Position
属性应该反映数据中的实际位置,如果不能做到这一点,它应该抛出NotSupportedException
异常。现在,让我们看看您的情况:
对同一个流使用
StreamReader
和StreamWriter
是很棘手的,而且大多数情况下流只有一个位置,并且将用于读取和写入,因此。您必须跟踪两个单独的位置。此外,您必须在每次读取和写入操作后刷新缓冲区,以便缓冲区中没有任何内容,并且流的位置是当您检索它时是最新的。这意味着StreamReader
和StreamWriter
无法按预期使用,而只能充当流的包装器。如果您从不同的线程使用
StreamReader
和StreamWriter
,则必须同步每个操作。两个线程永远不能同时使用流,因此读/写操作必须执行以下操作:流可以这种方式可以用作 FIFO 缓冲区,但还有其他方式可能更适合您的需求。例如,
Queue
用作内存中 FIFO 缓冲区。I think that you are expecting a little too much from the documentation. It does tell you exactly what everything does, but it doesn't tell you much about how to use it. If you are not familiar with streams, reading only the documention will not give you enough information to actually understand how to use them.
Let's look at what the documentation says:
This is "standard documentation speak" for saying that the property is intended for keeping track of the position in the stream, but that the
Stream
class itself doesn't provide the actual implementation of that. The implementation lies in classes that derive from theStream
class, like aFileStream
or aMemoryStream
. Each have their own system of maintaining the position, because they work against completely different back ends.There can even be implementation of streams where the
Position
property doesn't make sense. You can use theCanSeek
property to find out if a stream implementation supports a position.This means that the
Position
property represents an absolute position in the back end implementation, it's not just a counter of what's been read or written. The methods for reading and writing the stream uses the position to keep track of where to read or write, it's not the other way around.For a stream implementation that doesn't support a position, it could still have returned how many bytes have been read or written, but it doesn't. The
Position
property should reflect an actual place in the data, and if it can't do that it should throw aNotSupportedException
exception.Now, let's look at your case:
Using a
StreamReader
and aStreamWriter
against the same stream is tricky, and mostly pointless. The stream only has one position, and that will be used for both reading and writing, so you would have to keep track of two separate positions. Also, you would have to flush the buffer after each read and write operation, so that there is nothing left in the buffers and thePosition
of the stream is up to date when you retrieve it. This means that theStreamReader
andStreamWriter
can't be used as intended, and only act as a wrapper around the stream.If you are using the
StreamReader
andStreamWriter
from different threads, you have to synchronise every operation. Two threads can never use the stream at the same time, so a read/write operation would have to do:A stream can be used as a FIFO buffer that way, but there are other ways that may be better suited for your needs. A
Queue<T>
for example works as an in-memory FIFO buffer.Position
属性重置为 0 后,它将开始覆盖现有数据。一个
Stream
可能就像一个管道......这实际上取决于你用它做什么。目前尚不清楚您所说的“我是否必须继续复制上次读取后的数据”是什么意思 - 也不清楚您所说的缓冲区是什么意思。如果你能对你想要实现的目标有一个更大的了解,那将会很有帮助。
Position
property to 0, it will start overwriting existing dataA
Stream
may act like a pipe... it really depends on what you're doing with it. It's unclear what you mean by "do I have to keep copying the data past my last read" - and unclear what you mean by your buffer, too.If you could give an idea of the bigger picture of what you're trying to achieve, that would really help.