| 1 | using System;
|
|---|
| 2 | using System.IO;
|
|---|
| 3 | using System.Text;
|
|---|
| 4 | using System.Threading;
|
|---|
| 5 | using System.Diagnostics;
|
|---|
| 6 |
|
|---|
| 7 | namespace Common
|
|---|
| 8 | {
|
|---|
| 9 | internal delegate void UserCallBack(string data);
|
|---|
| 10 |
|
|---|
| 11 | internal class AsyncStreamReaderFast : IDisposable
|
|---|
| 12 | {
|
|---|
| 13 | internal const int DefaultBufferSize = 16000; // Byte buffer size
|
|---|
| 14 | private const int MinBufferSize = 128;
|
|---|
| 15 |
|
|---|
| 16 | private Stream stream;
|
|---|
| 17 | private Encoding encoding;
|
|---|
| 18 | private Decoder decoder;
|
|---|
| 19 | private byte[] byteBuffer;
|
|---|
| 20 | private char[] charBuffer;
|
|---|
| 21 | // Record the number of valid bytes in the byteBuffer, for a few checks.
|
|---|
| 22 |
|
|---|
| 23 | // This is the maximum number of chars we can get from one call to
|
|---|
| 24 | // ReadBuffer. Used so ReadBuffer can tell when to copy data into
|
|---|
| 25 | // a user's char[] directly, instead of our internal char[].
|
|---|
| 26 | private int maxCharsPerBuffer;
|
|---|
| 27 |
|
|---|
| 28 | // Store a backpointer to the process class, to check for user callbacks
|
|---|
| 29 | private StringBuilder sb;
|
|---|
| 30 |
|
|---|
| 31 | // Delegate to call user function.
|
|---|
| 32 | private UserCallBack userCallBack;
|
|---|
| 33 |
|
|---|
| 34 | // Internal Cancel operation
|
|---|
| 35 | private bool cancelOperation;
|
|---|
| 36 | private ManualResetEvent eofEvent;
|
|---|
| 37 |
|
|---|
| 38 | internal AsyncStreamReaderFast(Process process, Stream stream, UserCallBack callback, Encoding encoding)
|
|---|
| 39 | : this(process, stream, callback, encoding, DefaultBufferSize)
|
|---|
| 40 | {
|
|---|
| 41 | }
|
|---|
| 42 |
|
|---|
| 43 | // Creates a new AsyncStreamReader for the given stream. The
|
|---|
| 44 | // character encoding is set by encoding and the buffer size,
|
|---|
| 45 | // in number of 16-bit characters, is set by bufferSize.
|
|---|
| 46 | //
|
|---|
| 47 | internal AsyncStreamReaderFast(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize)
|
|---|
| 48 | {
|
|---|
| 49 | Debug.Assert(process != null && stream != null && encoding != null && callback != null, "Invalid arguments!");
|
|---|
| 50 | Debug.Assert(stream.CanRead, "Stream must be readable!");
|
|---|
| 51 | Debug.Assert(bufferSize > 0, "Invalid buffer size!");
|
|---|
| 52 |
|
|---|
| 53 | Init(process, stream, callback, encoding, bufferSize);
|
|---|
| 54 | }
|
|---|
| 55 |
|
|---|
| 56 | private void Init(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize)
|
|---|
| 57 | {
|
|---|
| 58 | this.stream = stream;
|
|---|
| 59 | this.encoding = encoding;
|
|---|
| 60 | this.userCallBack = callback;
|
|---|
| 61 | decoder = encoding.GetDecoder();
|
|---|
| 62 | if (bufferSize < MinBufferSize) bufferSize = MinBufferSize;
|
|---|
| 63 | byteBuffer = new byte[bufferSize];
|
|---|
| 64 | maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
|
|---|
| 65 | charBuffer = new char[maxCharsPerBuffer];
|
|---|
| 66 | cancelOperation = false;
|
|---|
| 67 | eofEvent = new ManualResetEvent(false);
|
|---|
| 68 | sb = new StringBuilder(charBuffer.Length);
|
|---|
| 69 | }
|
|---|
| 70 |
|
|---|
| 71 | public virtual void Close()
|
|---|
| 72 | {
|
|---|
| 73 | Dispose(true);
|
|---|
| 74 | }
|
|---|
| 75 |
|
|---|
| 76 | void IDisposable.Dispose()
|
|---|
| 77 | {
|
|---|
| 78 | Dispose(true);
|
|---|
| 79 | }
|
|---|
| 80 |
|
|---|
| 81 | protected virtual void Dispose(bool disposing)
|
|---|
| 82 | {
|
|---|
| 83 | if (disposing)
|
|---|
| 84 | {
|
|---|
| 85 | if (stream != null)
|
|---|
| 86 | stream.Close();
|
|---|
| 87 | }
|
|---|
| 88 | if (stream != null)
|
|---|
| 89 | {
|
|---|
| 90 | stream = null;
|
|---|
| 91 | encoding = null;
|
|---|
| 92 | decoder = null;
|
|---|
| 93 | byteBuffer = null;
|
|---|
| 94 | charBuffer = null;
|
|---|
| 95 | }
|
|---|
| 96 |
|
|---|
| 97 | if (eofEvent != null)
|
|---|
| 98 | {
|
|---|
| 99 | eofEvent.Close();
|
|---|
| 100 | eofEvent = null;
|
|---|
| 101 | }
|
|---|
| 102 | }
|
|---|
| 103 |
|
|---|
| 104 | public virtual Encoding CurrentEncoding => encoding;
|
|---|
| 105 |
|
|---|
| 106 | public virtual Stream BaseStream => stream;
|
|---|
| 107 |
|
|---|
| 108 | // User calls BeginRead to start the asynchronous read
|
|---|
| 109 | public void BeginRead()
|
|---|
| 110 | {
|
|---|
| 111 | if (cancelOperation)
|
|---|
| 112 | {
|
|---|
| 113 | cancelOperation = false;
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | stream.BeginRead(byteBuffer, 0, byteBuffer.Length, ReadBuffer, null);
|
|---|
| 117 | }
|
|---|
| 118 |
|
|---|
| 119 | internal void CancelOperation()
|
|---|
| 120 | {
|
|---|
| 121 | cancelOperation = true;
|
|---|
| 122 | }
|
|---|
| 123 |
|
|---|
| 124 | // This is the async callback function. Only one thread could/should call this.
|
|---|
| 125 | private void ReadBuffer(IAsyncResult ar)
|
|---|
| 126 | {
|
|---|
| 127 | int byteLen;
|
|---|
| 128 |
|
|---|
| 129 | try
|
|---|
| 130 | {
|
|---|
| 131 | byteLen = stream.EndRead(ar);
|
|---|
| 132 | }
|
|---|
| 133 | catch (IOException)
|
|---|
| 134 | {
|
|---|
| 135 | // We should ideally consume errors from operations getting cancelled
|
|---|
| 136 | // so that we don't crash the unsuspecting parent with an unhandled exc.
|
|---|
| 137 | // This seems to come in 2 forms of exceptions (depending on platform and scenario),
|
|---|
| 138 | // namely OperationCanceledException and IOException (for errorcode that we don't
|
|---|
| 139 | // map explicitly).
|
|---|
| 140 | byteLen = 0; // Treat this as EOF
|
|---|
| 141 | }
|
|---|
| 142 | catch (OperationCanceledException)
|
|---|
| 143 | {
|
|---|
| 144 | // We should consume any OperationCanceledException from child read here
|
|---|
| 145 | // so that we don't crash the parent with an unhandled exc
|
|---|
| 146 | byteLen = 0; // Treat this as EOF
|
|---|
| 147 | }
|
|---|
| 148 |
|
|---|
| 149 | if (byteLen == 0)
|
|---|
| 150 | {
|
|---|
| 151 | eofEvent.Set();
|
|---|
| 152 | }
|
|---|
| 153 | else
|
|---|
| 154 | {
|
|---|
| 155 | int charLen = decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, 0);
|
|---|
| 156 | sb.Length = 0;
|
|---|
| 157 | sb.Append(charBuffer, 0, charLen);
|
|---|
| 158 | userCallBack(sb.ToString());
|
|---|
| 159 | stream.BeginRead(byteBuffer, 0, byteBuffer.Length, ReadBuffer, null);
|
|---|
| 160 | }
|
|---|
| 161 | }
|
|---|
| 162 |
|
|---|
| 163 | // Wait until we hit EOF. This is called from Process.WaitForExit
|
|---|
| 164 | // We will lose some information if we don't do this.
|
|---|
| 165 | internal void WaitUtilEOF()
|
|---|
| 166 | {
|
|---|
| 167 | if (eofEvent == null) return;
|
|---|
| 168 | eofEvent.WaitOne();
|
|---|
| 169 | eofEvent.Close();
|
|---|
| 170 | eofEvent = null;
|
|---|
| 171 | }
|
|---|
| 172 | }
|
|---|
| 173 | }
|
|---|