C#: Asynchronous NamedPipeServerStream The pipe is being closed exception

烈酒焚心 提交于 2019-12-03 20:07:28
ShdNx

It appears that you need a separate NamedPipeServerStream for each client. (Note that I was not the one to discover this, see the other answers.) I'd imagine the working server-side would look something like this (draft code):

while(this.isServerRunning)
{
     var pipeClientConnection = new NamedPipeServerStream(...);

     try
     {
         pipeClientConnection.WaitForConnection();
     }
     catch(...)
     {
         ...
         continue;
     }

     ThreadPool.QueueUserWorkItem(state =>
          {
               // we need a separate variable here, so as not to make the lambda capture the pipeClientConnection variable, which is not recommended in multi-threaded scenarios
               using(var pipeClientConn = (NamedPipeServerStream)state)
               {
                    // do stuff
                    ...
               }
          }, pipeClientConnection);
}

As a side note, as it was pointed out in a comment to your question, you're wasting memory with initiating a new async call every 3 seconds by calling BeginWaitForConnection in a loop (the only case where this wouldn't waste memory is when new connections are made in intervals smaller than 3 seconds, but I doubt that you can know this for sure). You see, basically every 3 seconds you're initiating a new async call, regardless of whether the last one is still pending or has completed. Furthermore, it - once again - does not take into account that you need a separate NamedPipeServerStream for each client.

To fix this issue, you need to eliminate the loop, and "chain" the BeginWaitForConnection calls using the callback method. This is a similar pattern you'll see quite often in async I/O when using .NET. Draft code:

private void StartListeningPipes()
{
    if(!this.isServerRunning)
    {
        return;
    }

    var pipeClientConnection = new NamedPipeServerStream(...);

    try
    {
        pipeClientConnection.BeginWaitForConnection(asyncResult =>
            {
                // note that the body of the lambda is not part of the outer try... catch block!
                using(var conn = (NamedPipeServerStream)asyncResult.AsyncState)
                {
                    try
                    {
                        conn.EndWaitForConnection(asyncResult);
                    }
                    catch(...)
                    {
                        ...
                    }

                    // we have a connection established, time to wait for new ones while this thread does its business with the client
                    // this may look like a recursive call, but it is not: remember, we're in a lambda expression
                    // if this bothers you, just export the lambda into a named private method, like you did in your question
                    StartListeningPipes();

                    // do business with the client
                    conn.WaitForPipeDrain();
                    ...
                }
            }, pipeClientConnection);
    }
    catch(...)
    {
        ...
    }
}

The control flow will be something like this:

  • [main thread] StartListeningPipes(): created NamedPipeServerStream, initiated BeginWaitForConnection()
  • [threadpool thread 1] client #1 connecting, BeginWaitForConnection() callback: EndWaitForConnection() then StartListeningPipes()
  • [threadpool thread 1] StartListeningPipes(): created new NamedPipeServerStream, BeginWaitForConnection() call
  • [threadpool thread 1] back to the BeginWaitForConnection() callback: getting down to business with the connected client (#1)
  • [threadpool thread 2] client #2 connecting, BeginWaitForConnection() callback: ...
  • ...

I think that this is a lot more difficult than using blocking I/O - in fact, I'm not quite certain I got it right, please point it out if you see any mistakes - and it's also a lot more confusing.

To pause the server in either examples, you obviously would set the this.isServerRunning flag to false.

Ksice

Ok. Stupied me. There should be one NamedPipeServerStream for each client. So if Async operation was completed, then have to recreate NamedPipeServerStream. Thanks this Multithreaded NamePipeServer in C#

Should be:

while(isPipeWorking)
            {
                IAsyncResult asyncResult = namedPipeServerStream.BeginWaitForConnection(this.WaitForConnectionAsyncCallback, null);
                Thread.Sleep(3*1000);
                if (asyncResult.IsCompleted)
                {
                    RestartPipeServer();
                    break;
                }
            }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!