简体   繁体   English

StreamReader.Peek和Thread.Interrupt的替代方案

[英]Alternative to StreamReader.Peek and Thread.Interrupt

Quick preface of what I'm trying to do. 我正在尝试做的快速序言。 I want to start a process and start up two threads to monitor the stderr and stdin. 我想启动一个进程并启动两个线程来监视stderr和stdin。 Each thread chews off bits of the stream and then fires it out to a NetworkStream. 每个线程都会扼杀流中的一些内容,然后将其发送到NetworkStream。 If there is an error in either thread, both threads need to die immediately. 如果任一线程中都有错误,则两个线程都需要立即死亡。

Each of these processes with stdout and stdin monitoring threads are spun off by a main server process. 具有stdout和stdin监视线程的这些进程中的每一个都由主服务器进程分离。 The reason this becomes tricky is because there can easily be 40 or 50 of these processes at any given time. 这变得棘手的原因是因为在任何给定时间都可以容易地有40或50个这样的过程。 Only during morning restart bursts are there ever more than 50 connections, but it really needs to be able to handle 100 or more. 只有在早上重新启动时才会有超过50个连接,但它确实需要能够处理100个或更多。 I test with 100 simultaneous connections. 我测试100个同时连接。

try
{
    StreamReader reader = this.myProcess.StandardOutput;

    char[] buffer = new char[4096];
    byte[] data;
    int read;

    while (reader.Peek() > -1 ) // This can block before stream is streamed to
    {
        read = reader.Read(buffer, 0, 4096);
        data = Server.ClientEncoding.GetBytes(buffer, 0, read);
        this.clientStream.Write(data, 0, data.Length); //ClientStream is a NetworkStream
    }
}
catch (Exception err)
{
        Utilities.ConsoleOut(string.Format("StdOut err for client {0} -- {1}", this.clientID, err));
        this.ShutdownClient(true);
}

This code block is run in one Thread which is right now not Background. 此代码块在一个Thread中运行,该Thread现在不是Background。 There is a similar thread for the StandardError stream. StandardError流有一个类似的线程。 I am using this method instead of listening to OutputDataReceived and ErrorDataReceived because there was an issue in Mono that caused these events to not always fire properly and even though it appears to be fixed now I like that this method ensures I'm reading and writing everything sequentially. 我正在使用此方法而不是侦听OutputDataReceived和ErrorDataReceived,因为Mono中存在导致这些事件不能始终正常触发的问题,即使它现在似乎已修复,我也喜欢这种方法确保我正在阅读和编写所有内容顺序。

ShutdownClient with True simply tries to kill both threads. ShutdownClient with True只是试图杀死两个线程。 Unfortunately the only way I have found to make this work is to use an interrupt on the stdErrThread and stdOutThread objects. 不幸的是,我发现使这项工作的唯一方法是在stdErrThread和stdOutThread对象上使用中断。 Ideally peek would not block and I could just use a manual reset event to keep checking for new data on stdOut or stdIn and then just die when the event is flipped. 理想情况下,peek不会阻塞,我可以使用手动重置事件来继续检查stdOut或stdIn上的新数据,然后在事件被翻转时死掉。

I doubt this is the best way to do it. 我怀疑这是最好的方法。 Is there a way to execute this without using an Interrupt? 有没有办法在不使用中断的情况下执行此操作?

I'd like to change, because I just saw in my logs that I missed a ThreadInterruptException thrown inside Utlities.ConsoleOut. 我想改变,因为我刚刚在日志中看到我错过了在Utlities.ConsoleOut中抛出的ThreadInterruptException。 This just does a System.Console.Write if a static variable is true, but I guess this blocks somewhere. 如果静态变量为true,这只是一个System.Console.Write,但我想这会阻塞某个地方。

Edits: 编辑:

These threads are part of a parent Thread that is launched en masse by a server upon a request. 这些线程是父线程的一部分,它由服务器根据请求一起启动。 Therefore I cannot set the StdOut and StdErr threads to background and kill the application. 因此,我无法将StdOut和StdErr线程设置为后台并终止应用程序。 I could kill the parent thread from the main server, but this again would get sticky with Peek blocking. 我可以从主服务器中删除父线程,但是这会再次与Peek阻塞相关。

Added info about this being a server. 添加了有关此服务器的信息。

Also I'm starting to realize a better Queuing method for queries might be the ultimate solution. 此外,我开始意识到更好的查询排队方法可能是最终的解决方案。

I can tell this whole mess stems from the fact that Peek blocks. 我可以说这整个混乱源于Peek阻止的事实。 You're really trying to fix something that is fundamentally broken in the framework and that is never easy (ie not a dirty hack). 你真的试图修复一些在框架中根本被破坏的东西,这从来都不容易(即不是一个肮脏的黑客攻击)。 Personally, I would fix the root of the problem, which is the blocking Peek . 就个人而言,我会解决问题的根源,即阻塞Peek Mono would've followed Microsoft's implementation and thus ends up with the same problem. Mono将遵循微软的实施,因此最终会遇到同样的问题。

While I know exactly how to fix the problem should I be allowed to change the framework source code, the workaround is lengthy and time consuming. 虽然我知道如何解决问题,但是我应该允许更改框架源代码,但解决方法是漫长而耗时的。

But here goes. 但是这里。

Essentially, what Microsoft needs to do is change Process.StartWithCreateProcess such that standardOutput and standardError are both assigned a specialised type of StreamReader (eg PipeStreamReader ). 从本质上讲,Microsoft需要做的是更改Process.StartWithCreateProcess ,以便为standardOutputstandardError分配一个特殊类型的StreamReader (例如PipeStreamReader )。

In this PipeStreamReader , they need to override both ReadBuffer overloads (ie need to change both overloads to virtual in StreamReader first) such that prior to a read, PeekNamedPipe is called to do the actual peek. 在这个PipeStreamReader ,它们需要覆盖两个ReadBuffer重载(即需要首先将两个重载更改为StreamReader虚拟),以便在读取之前调用PeekNamedPipe进行实际查看。 As it is at the moment, FileStream.Read() (called by Peek() ) will block on pipe reads when no data is available for read. 就像现在一样,当没有可用于读取的数据时, FileStream.Read() (由Peek()调用)将阻塞管道读取。 While a FileStream.Read() with 0 bytes works well on files, it doesn't work all that well on pipes. 虽然具有0字节的FileStream.Read()在文件上运行良好,但在管道上却无法正常工作。 In fact, the .NET team missed an important part of the pipe documentation - PeekNamedPipe WinAPI. 事实上,.NET团队错过了管道文档的重要部分 - PeekNamedPipe WinAPI。

The PeekNamedPipe function is similar to the ReadFile function with the following exceptions: PeekNamedPipe函数类似于ReadFile函数,但有以下例外:

... ...

The function always returns immediately in a single-threaded application, even if there is no data in the pipe . 即使管道中没有数据,该函数也会立即在单线程应用程序中返回。 The wait mode of a named pipe handle (blocking or nonblocking) has no effect on the function. 命名管道句柄的等待模式(阻塞或非阻塞)对该功能没有影响。

The best thing at this moment without this issue solved in the framework would be to roll out your own Process class (a thin wrapper around WinAPI would suffice). 此时没有在框架中解决此问题的最好的事情就是推出自己的Process类(围绕WinAPI的瘦包装就足够了)。

You're building a server. 你正在构建一个服务器。 You want to avoid blocking. 你想避免阻止。 The obvious solution is to use the asynchronous APIs: 显而易见的解决方案是使用异步API:

var myProcess = Process.GetCurrentProcess();
StreamReader reader = myProcess.StandardOutput;

char[] buffer = new char[4096];
byte[] data;
int read;

while (!myProcess.HasExited)
{
    read = await reader.ReadAsync(buffer, 0, 4096);
    data = Server.ClientEncoding.GetBytes(buffer, 0, read);

    await this.clientStream.WriteAsync(data, 0, data.Length);
}

No need to waste threads doing I/O work :) 不需要浪费线程进行I / O工作:)

Why dont you just set both Threads to be backround and then kill the app? 你为什么不把两个线程都设置为背景然后杀死应用程序? It would cause an immediate closing of both threads. 它会导致两个线程立即关闭。

Get rid of peek and use the method below to read from the process output streams. 摆脱窥视并使用下面的方法从过程输出流中读取。 ReadLine() returns null when the process ends. ReadLine()在进程结束时返回null。 To join this thread with your calling thread either wait for the process to end or kill the process yourself. 要与调用线程一起加入此线程,请等待进程结束或自行终止进程。 ShutdownClient() should just Kill() the process which will cause the other thread reading the StdOut or StdErr to also exit. ShutdownClient()应该只是Kill()进程,这将导致读取StdOut或StdErr的另一个线程也退出。

    private void ReadToEnd()
    {
        string nextLine;
        while ((nextLine = stream.ReadLine()) != null)
        {
             output.WriteLine(nextLine);
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM