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

时间:2010-12-29 20:29:32

标签: c# mono

我正在尝试做的快速序言。我想启动一个进程并启动两个线程来监视stderr和stdin。每个线程都会扼杀流中的一些内容,然后将其发送到NetworkStream。如果任一线程中都有错误,则两个线程都需要立即死亡。

这些具有stdout和stdin监视线程的进程中的每一个都由主服务器进程分离出来。这变得棘手的原因是因为在任何给定时间都可以容易地有40或50个这样的过程。只有在早上重新启动时才会有超过50个连接,但它确实需要能够处理100个或更多。我测试了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);
}

此代码块在一个Thread中运行,该Thread现在不是Background。 StandardError流有一个类似的线程。我正在使用此方法而不是侦听OutputDataReceived和ErrorDataReceived,因为Mono中存在导致这些事件不能始终正常触发的问题,即使它现在似乎已修复,我也喜欢这种方法确保我正在阅读和编写所有内容顺序。

ShutdownClient with True只是试图杀死两个线程。不幸的是,我发现使这项工作的唯一方法是在stdErrThread和stdOutThread对象上使用中断。理想情况下,peek不会阻塞,我可以使用手动重置事件继续检查stdOut或stdIn上的新数据,然后在事件被翻转时死掉。

我怀疑这是最好的方法。有没有办法在不使用中断的情况下执行此操作?

我想改变,因为我刚刚在日志中看到我错过了在Utlities.ConsoleOut中抛出的ThreadInterruptException。如果静态变量为true,这只是一个System.Console.Write,但我猜这会阻塞某个地方。

编辑:

这些线程是父线程的一部分,该线程由服务器在请求时大量启动。因此,我无法将StdOut和StdErr线程设置为后台并终止应用程序。我可以从主服务器中删除父线程,但是这会再次与Peek阻塞相关。

添加了有关此服务器的信息。

此外,我开始意识到更好的查询排队方法可能是最终的解决方案。

4 个答案:

答案 0 :(得分:2)

我可以说这整个混乱源于Peek阻塞的事实。你真的试图修复一些在框架中根本被破坏的东西,这从来都不容易(即不是一个肮脏的黑客攻击)。就个人而言,我会解决问题的根源,即阻塞Peek。 Mono会跟随微软的实施,因此也会遇到同样的问题。

虽然我确切知道如何解决问题,但是我应该允许更改框架源代码,但解决方法是漫长而耗时的。

但是这里有。

基本上,Microsoft需要做的是更改Process.StartWithCreateProcess,以便为standardOutputstandardError分配一个特殊类型的StreamReader(例如PipeStreamReader

在这个PipeStreamReader中,他们需要覆盖两个ReadBuffer重载(即需要先在StreamReader中将两个重载更改为虚拟),以便在读取之前{{1}被称为做实际的偷看。就目前而言,当没有可用于读取的数据时,PeekNamedPipe(由FileStream.Read()调用)将阻止管道读取。虽然具有0字节的Peek()在文件上运行良好,但它在管道上的效果不佳。事实上,.NET团队错过了管道文档的重要部分 - PeekNamedPipe WinAPI。

  

PeekNamedPipe函数类似于ReadFile函数,但有以下例外:

     

...

     

该函数始终在单线程应用程序中立即返回, 即使管道中没有数据 。命名管道句柄的等待模式(阻塞或非阻塞)对函数没有影响。

此时没有在框架中解决此问题的最好的事情就是推出自己的Process类(围绕WinAPI的瘦包装就足够了)。

答案 1 :(得分:0)

为什么不将两个线程都设置为背景然后杀死应用程序?它会导致两个线程立即关闭。

答案 2 :(得分:0)

您正在构建服务器。你想避免阻止。显而易见的解决方案是使用异步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);
}

无需浪费线程进行I / O工作:)

答案 3 :(得分:-1)

摆脱窥视并使用下面的方法从过程输出流中读取。 ReadLine()在进程结束时返回null。要与调用线程一起加入此线程,请等待进程结束或自行终止进程。 ShutdownClient()应该只是Kill()进程,这将导致读取StdOut或StdErr的另一个线程也退出。

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