C# 多线程生产者/消费者

C# 多线程生产者/消费者,c#,producer-consumer,C#,Producer Consumer,我有一个代理服务器,我正在用C#编写。我还有一个Java小程序,它通过这个C#代理从视频服务器接收MJPEG数据。我的问题是,当视频服务器当前没有更多的MJPEG数据可用时,代理卡在阻塞读取调用中,我无法终止它 // write the forwarded output // blocking on remoteServerResponseStream.Read while (m_running && (read = remoteServerResponseStream.Read

我有一个代理服务器,我正在用C#编写。我还有一个Java小程序,它通过这个C#代理从视频服务器接收MJPEG数据。我的问题是,当视频服务器当前没有更多的MJPEG数据可用时,代理卡在阻塞读取调用中,我无法终止它

// write the forwarded output
// blocking on remoteServerResponseStream.Read
while (m_running && (read = remoteServerResponseStream.Read(buffer, 0, buffer.Length)) > 0)
{
    bytesRead += read;

    output.Write(buffer, 0, read);

    output.Flush();
}
这应该通过Java小程序关闭流来终止(上面代码中的变量
output
)。但是,Java小程序无法关闭此连接,因为当代理卡在
remoteServerResponseStream.Read
等待数据时,代理从未确认关闭请求

我在这个问题上纠缠了一个星期。我想我可能已经想到了一个解决办法,但我不确定它是否有效。我很想听到任何关于这方面的反馈


我的想法是在另一个线程上删除
removeServerResponseStream.Read
,并使用共享队列传输数据。线程将读取数据并将其放在队列中。然后,我的主线程会将队列中的任何可用数据转发到
输出
。这样,我可以不断地检查
output.CanWrite
是否变为false,在这种情况下,我可以中止读取线程(这是我知道的唯一中断阻塞流读取的方法)。这是一个可行的解决方案吗?如果是这样,我应该不断轮询队列中的可用数据,还是创建一个事件?我很想听听大家对这个问题的看法!提前感谢。

在这种情况下,我只是从另一个线程结束流阅读器。当你关闭读卡器时,它将中断循环。通过额外的同步队列只是不必要地做更多的样板。最终,你必须在某个地方停止阻塞读卡器。将值放入队列只会阻止您的消费者,而不会阻止您的阅读器

下面是一个套接字阻止读取的示例。我将阻止读取1字节,我将故意不发送。几秒钟后,我将处理该套接字,该套接字将退出其阻塞读取,应用程序将优雅地退出。我将记录每个线程正在做什么以及何时发生(每个日志行前面的数字是线程id,我将缩进单独的线程)

在使用阻塞读取器的类中,您应该实现一次性模式,并在那里关闭/处置读取器

static void Main(string[] args)
{
    SocketTest();

    Console.WriteLine("Press any key to exit");
    Console.ReadKey();
}

public static void SocketTest()
{
    int port = 22345;

    var tcpListener = new TcpListener(IPAddress.Any, port);

    tcpListener.Start();

    // Listening thread
    new Thread(() =>
    {

        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " - Waiting for connection to port");

        var socket = tcpListener.AcceptSocket();

        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " - Connection accepted");

        var stream = new NetworkStream(socket);
        var reader = new BinaryReader(stream);

        try
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " - Starting blocking read");
            var bytes = reader.ReadBytes(1);
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " - Done blocking read, read {0} bytes", bytes.Length);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error reading " + ex);
        }
    }).Start();

    // connecting thread
    new Thread(() =>
    {
        var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        Console.WriteLine("\t" + Thread.CurrentThread.ManagedThreadId + " - Connecting to local port");

        socket.Connect("127.0.0.1", port);

        Console.WriteLine("\t" + Thread.CurrentThread.ManagedThreadId + " - Connecting to local succeeded");

        Thread.Sleep(TimeSpan.FromSeconds(2));

        Console.WriteLine("\t" + Thread.CurrentThread.ManagedThreadId + " - Disposing of socket");

        socket.Dispose();

    }).Start();

    Thread.Sleep(TimeSpan.FromSeconds(5));
}
运行此操作时,您会得到:

3 - Waiting for connection to port
        4 - Connecting to local port
        4 - Connecting to local succeeded
3 - Connection accepted
3 - Starting blocking read
        4 - Disposing of socket
3 - Done blocking read, read 0 bytes
Press any key to exit
不过,你自己在这里回答了这个问题:

我的想法是使用removeServerResponseStream。阅读另一个线程

这正是你应该做的。当您知道应用程序已关闭时,请从活动线程关闭套接字。这个空闲线程是阻塞线程,您可以优雅地结束它


这里的模式是,您通常为特定套接字启动一个线程,并维护一个活动线程,该线程是一种请求的“控制器”线程

您应该使用.NET 4.0任务并行库引入的并发集合和其他容器类。我们需要额外的代码。我的问题是我不知道何时处理套接字。当视频剪辑结束时,视频服务器停止发送数据,但保持连接活动,因为视频服务器可以接收命令以查找剪辑中的不同位置。例如,如果它接收到一个命令,从一开始就搜索剪辑,它只会再次开始通过相同的连接发送数据。因此,不通过连接发送数据并不一定意味着我们应该关闭它。当Java小程序关闭
输出时,我需要关闭连接。听起来您确实知道何时处理套接字:当小程序关闭时。如果无法确定小程序何时断开连接,则可以在套接字上指定一个超时。如果您在已知时间内没有获得任何数据,套接字将自动关闭,但如果该值不正确,您将不得不重新连接。你能让你的小程序有一个ping吗?如果在一定时间内没有收到ping,控制器线程可以关闭相应的打开的套接字。那么我就有点困在使用共享队列中了,对吗?要将数据从读取线程传递到“使用者”或“输出”线程?另外,如果我关闭了与Java小程序的连接,我的“输出”线程中是否会出现异常,从那里我可以正确地处理读取套接字?顺便说一句,谢谢你的帮助。不,看看我的例子。没有引发异常,这是标准的套接字关闭过程。您不需要共享队列,仅仅因为数据在另一个线程中并不意味着您必须对其进行排队。您的对象在线程中共享。如果您所做的只是读取数据,然后将其写出来,只要该编写器未在其他线程中使用,您就可以了。感谢您的帮助devshorts。