Skip to content

Commit

Permalink
Обертка для асинхронного чтения потоков процесса
Browse files Browse the repository at this point in the history
  • Loading branch information
EvilBeaver committed Feb 12, 2017
1 parent d49d183 commit c4be395
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 10 deletions.
17 changes: 13 additions & 4 deletions src/ScriptEngine.HostedScript/Library/ProcessContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ public StdTextReadStream StdOut
{
get
{
if(_stdOutContext == null)
_stdOutContext = new StdTextReadStream(_p.StandardOutput);

if (_stdOutContext == null)
{
var stream = new ProcessOutputWrapper(_p, ProcessOutputWrapper.OutputVariant.Stdout);
stream.StartReading();

_stdOutContext = new StdTextReadStream(stream);
}
return _stdOutContext;
}
}
Expand All @@ -74,7 +78,12 @@ public StdTextReadStream StdErr
get
{
if (_stdErrContext == null)
_stdErrContext = new StdTextReadStream(_p.StandardError);
{
var stream = new ProcessOutputWrapper(_p, ProcessOutputWrapper.OutputVariant.Stderr);
stream.StartReading();

_stdErrContext = new StdTextReadStream(stream);
}
return _stdErrContext;
}
}
Expand Down
224 changes: 224 additions & 0 deletions src/ScriptEngine.HostedScript/Library/ProcessOutputWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

using sys = System.Diagnostics;

namespace ScriptEngine.HostedScript.Library
{
class ProcessOutputWrapper : TextReader
{
private sys.Process _process;
private OutputVariant _variant;
private StringBuilder _buffer = new StringBuilder(4096);
private ReaderWriterLockSlim _locker;

private int _bufferIndex = 0;

private bool AlreadyReading { get; set; }

private Encoding Encoding { get; set; }

public enum OutputVariant
{
Stdout,
Stderr
}

public ProcessOutputWrapper(sys.Process process, OutputVariant variant)
{
_process = process;
_variant = variant;
_locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
}

public void StartReading()
{
if (AlreadyReading)
return;

if (_variant == OutputVariant.Stdout)
{
Encoding = _process.StartInfo.StandardOutputEncoding;
_process.BeginOutputReadLine();
_process.OutputDataReceived += StreamDataReceived;
}
else
{
Encoding = _process.StartInfo.StandardErrorEncoding;
_process.BeginErrorReadLine();
_process.ErrorDataReceived += StreamDataReceived;
}

AlreadyReading = true;
}

private void StopReading()
{
if (_variant == OutputVariant.Stdout)
{
_process.OutputDataReceived -= StreamDataReceived;
}
else
{
_process.ErrorDataReceived -= StreamDataReceived;
}
}

private void StreamDataReceived(object sender, sys.DataReceivedEventArgs e)
{
try
{
_locker.EnterWriteLock();
{
if (e.Data != null)
{
if (_buffer.Length != 0)
_buffer.Append(System.Environment.NewLine);

_buffer.Append(e.Data);
}
}
}
finally
{
if(_locker.IsWriteLockHeld) // При else бросит правильное исключение, из-за которого не захватил блокировку
_locker.ExitWriteLock();
}
}

public override int Peek()
{
try
{
_locker.EnterReadLock();
if (_bufferIndex >= _buffer.Length)
return -1; // no data

return _buffer[_bufferIndex];
}
finally
{
if (_locker.IsReadLockHeld) // При else бросит правильное исключение, из-за которого не захватил блокировку
_locker.ExitReadLock();
}

}

public override int Read()
{
try
{
_locker.EnterReadLock();
return ReadInternal();
}
finally
{
if (_locker.IsReadLockHeld)
_locker.ExitReadLock();
}
}

private int ReadInternal()
{
if (_bufferIndex < _buffer.Length)
return _buffer[_bufferIndex++];

return -1;
}

public override int Read(char[] destBuffer, int index, int count)
{
if (destBuffer == null)
throw new ArgumentNullException(nameof(destBuffer));
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index), "Index is below zero");
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), "Count is below zero");
if (destBuffer.Length - index < count)
throw new ArgumentException("Invalid offset");

try
{
_locker.EnterReadLock();
int n = 0;
do
{
int ch = ReadInternal();
if (ch == -1) break;

destBuffer[index + n++] = (char) ch;
} while (n < count);

return n;
}
finally
{
if (_locker.IsReadLockHeld)
_locker.ExitReadLock();
}
}

public override string ReadLine()
{
try
{
_locker.EnterReadLock();
var sb = new StringBuilder();
while (true)
{
int ch = ReadInternal();
if (ch == -1) break;
if (ch == '\r' || ch == '\n')
{
if (ch == '\r' && Peek() == '\n') Read();
return sb.ToString();
}
sb.Append((char)ch);
}
if (sb.Length > 0) return sb.ToString();
return null;
}
finally
{
if (_locker.IsReadLockHeld)
_locker.ExitReadLock();
}
}

public override string ReadToEnd()
{
try
{
_locker.EnterReadLock();
string data = base.ReadToEnd();
ResetBuffer();
return data;
}
finally
{
if (_locker.IsReadLockHeld)
_locker.ExitReadLock();
}
}

private void ResetBuffer()
{
_buffer.Clear();
_bufferIndex = 0;
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
StopReading();
}

base.Dispose(disposing);
}
}
}
6 changes: 3 additions & 3 deletions src/ScriptEngine.HostedScript/Library/StdTextReadStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ namespace ScriptEngine.HostedScript.Library
[ContextClass("ПотокВыводаТекста","TextOutputStream")]
public class StdTextReadStream : AutoContext<StdTextReadStream>, IDisposable
{
private readonly StreamReader _reader;
private readonly TextReader _reader;

public StdTextReadStream(StreamReader source)
public StdTextReadStream(TextReader source)
{
_reader = source;
}
Expand All @@ -40,7 +40,7 @@ public bool HasData
{
get
{
return !_reader.EndOfStream;
return _reader.Peek() != -1;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
<Compile Include="Library\Net\TCPClient.cs" />
<Compile Include="Library\Net\TCPServer.cs" />
<Compile Include="Library\ProcessContext.cs" />
<Compile Include="Library\ProcessOutputWrapper.cs" />
<Compile Include="Library\Regex\MatchCollectionImpl.cs" />
<Compile Include="Library\Regex\MatchImpl.cs" />
<Compile Include="Library\Regex\Regex.cs" />
Expand Down Expand Up @@ -224,9 +225,7 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Library\TypeDescription\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
Expand Down

0 comments on commit c4be395

Please sign in to comment.