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 | }
|
---|