What is a good way to shutdown Threads blocked on NamedPipeServer#WaitForConnection?
Asked Answered
M

7

46

I start my application which spawns a number of Threads, each of which creates a NamedPipeServer (.net 3.5 added managed types for Named Pipe IPC) and waits for clients to connect (Blocks). The code functions as intended.

private void StartNamedPipeServer()
  {
    using (NamedPipeServerStream pipeStream =
                    new NamedPipeServerStream(m_sPipeName, PipeDirection.InOut, m_iMaxInstancesToCreate, PipeTransmissionMode.Message, PipeOptions.None))
    {
      m_pipeServers.Add(pipeStream);
      while (!m_bShutdownRequested)
      {
        pipeStream.WaitForConnection();
        Console.WriteLine("Client connection received by {0}", Thread.CurrentThread.Name);
        ....  

Now I also need a Shutdown method to bring this process down cleanly. I tried the usual bool flag isShutdownRequested trick. But the pipestream stays blocked on the WaitForConnection() call and the thread doesn't die.

public void Stop()
{
   m_bShutdownRequested = true;
   for (int i = 0; i < m_iMaxInstancesToCreate; i++)
   {
     Thread t = m_serverThreads[i];
     NamedPipeServerStream pipeStream = m_pipeServers[i];
     if (pipeStream != null)
     {
       if (pipeStream.IsConnected)
          pipeStream.Disconnect();
       pipeStream.Close();
       pipeStream.Dispose();
     }

     Console.Write("Shutting down {0} ...", t.Name);
     t.Join();
     Console.WriteLine(" done!");
   }
} 

Join never returns.

An option that I didnt try but would possibly work is to call Thread.Abort and eat up the exception. But it doesn't feel right.. Any suggestions

Update 2009-12-22
Sorry for not posting this earlier.. This is what I received as a response from Kim Hamilton (BCL team)

The "right" way to do an interruptible WaitForConnection is to call BeginWaitForConnection, handle the new connection in the callback, and close the pipe stream to stop waiting for connections. If the pipe is closed, EndWaitForConnection will throw ObjectDisposedException which the callback thread can catch, clean up any loose ends, and exit cleanly.

We realize this must be a common question, so someone on my team is planning to blog about this soon.

Mays answered 3/3, 2009 at 19:52 Comment(0)
B
18

Switch to the asynchronous version: BeginWaitForConnection.

If it does ever complete, you'll need a flag so the completion handler can just call EndWaitForConnection absorbing any exceptions and exiting (call End... to ensure any resources are able to be cleaned up).

Bathetic answered 4/3, 2009 at 16:49 Comment(1)
If you want sync style usage with this, after calling the Begin function you can wait on the IAsyncResult.AsyncWaitHandle along with your own 'cancel' ManualResetEvent using WaitHandle.WaitAny. Then at shutdown set your cancel event and your thread will get unblocked.Goods
K
51

This is cheesy, but it is the only method I have gotten to work. Create a 'fake' client and connect to your named pipe to move past the WaitForConnection. Works every time.

Also, even Thread.Abort() did not fix this issue for me.


_pipeserver.Dispose();
_pipeserver = null;

using (NamedPipeClientStream npcs = new NamedPipeClientStream("pipename")) 
{
    npcs.Connect(100);
}
Kuban answered 28/7, 2009 at 2:36 Comment(3)
works great without rewriting synchrous working code;-)) thanksFervent
This is a very simple solution... worked perfectly for me.Orifice
Cheesy, check. Works, check. In use should have error catching on that connect call.Blandishments
B
18

Switch to the asynchronous version: BeginWaitForConnection.

If it does ever complete, you'll need a flag so the completion handler can just call EndWaitForConnection absorbing any exceptions and exiting (call End... to ensure any resources are able to be cleaned up).

Bathetic answered 4/3, 2009 at 16:49 Comment(1)
If you want sync style usage with this, after calling the Begin function you can wait on the IAsyncResult.AsyncWaitHandle along with your own 'cancel' ManualResetEvent using WaitHandle.WaitAny. Then at shutdown set your cancel event and your thread will get unblocked.Goods
M
8

You can use the following extension method. Note the inclusion of 'ManualResetEvent cancelEvent' - you can set this event from another thread to signal that the waiting connect method should abort now and close the pipe. Include cancelEvent.Set() when setting m_bShutdownRequested and the shutdown should be relatively graceful.

    public static void WaitForConnectionEx(this NamedPipeServerStream stream, ManualResetEvent cancelEvent)
    {
        Exception e = null;
        AutoResetEvent connectEvent = new AutoResetEvent(false);
        stream.BeginWaitForConnection(ar =>
        {
            try
            {
                stream.EndWaitForConnection(ar);
            }
            catch (Exception er)
            {
                e = er;
            }
            connectEvent.Set();
        }, null);
        if (WaitHandle.WaitAny(new WaitHandle[] { connectEvent, cancelEvent }) == 1)
            stream.Close();
        if (e != null)
            throw e; // rethrow exception
    }
Matelote answered 7/5, 2012 at 15:57 Comment(1)
You can simplify this by calling BeginWaitForConnection without a callback (use null instead) then use WaitHandle.WaitAny on the returned result's AsyncWaitHandle and the cancelEvent.Wellbeloved
P
1

I wrote this extension method to solve this problem:

public static void WaitForConnectionEx(this NamedPipeServerStream stream)
{
    var evt = new AutoResetEvent(false);
    Exception e = null;
    stream.BeginWaitForConnection(ar => 
    {
        try
        {
            stream.EndWaitForConnection(ar);
        }
        catch (Exception er)
        {
            e = er;
        }
        evt.Set();
    }, null);
    evt.WaitOne();
    if (e != null)
        throw e; // rethrow exception
}
Pangenesis answered 8/9, 2011 at 11:41 Comment(2)
What does your method aim to achieve? Currently, its just a re-implementation of the synchronous WaitForConnection using the asynchronous counterparts.Jerriejerrilee
If you call stream.Dispose(), when WaitForConnection blocks some thread - nothing will happen, thread will be blocked. If you call stream.Dispose(), when some thread execute my extension method - WaitForConnectionEx will throw an exception.Pangenesis
F
1

An simplest and easy solution is creating a dummy client and do a connection with the server.

NamedPipeServerStream pServer;
bool exit_flg=false;
    public void PipeServerWaiter()
{

    NamedPipeServerStream  pipeServer = new NamedPipeServerStream("DphPipe", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances);
    pServer = pipeServer;
    pipeServer.WaitForConnection();


    if (exit_flg) return;
    thread = new Thread(PipeServerWaiter);
    thread.Start();

}
public void Dispose()
{
    try
    {
        exit_flg = true;
        NamedPipeClientStream clt = new NamedPipeClientStream(".", "DphPipe");
        clt.Connect();
        clt.Close();

        pServer.Close();
        pServer.Dispose();


    }
Feudal answered 17/4, 2016 at 4:23 Comment(0)
O
0

One way that could work is checking for m_bShutdownRequested right after the WaitForConnection.

During the shutdown process set the bool. After that send dummy messages to all the existing pipes so they open the connection and check the bool and shut down cleanly.

Oiler answered 14/7, 2009 at 1:23 Comment(1)
issue is that WaitForConnection is blocking and therefore you cannot check bool value after WaitForConnection because it does not return and stay hanged. It looks a critical issue of editor because there is no such stupid behavior on socketsPumpkin
C
-1
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

namespace PIPESERVER
{
    public partial class PWIN : UserControl
   {
    public string msg = "", cmd = "", text = "";
    public NamedPipeServerStream pipe;
    public NamedPipeClientStream dummyclient;
    public string PipeName = "PIPE1";
    public static string status = "";
    private static int numThreads = 2;
    int threadId;
    int i;
    string[] word;
    char[] buffer;
    public StreamString ss;

    public bool ConnectDummyClient()
    {
        new Thread(() =>
        {
            dummyclient = new NamedPipeClientStream(".", "PIPE1");
            try
            {
                dummyclient.Connect(5000); // 5 second timeout
            }
            catch (Exception e)
            {
                Act.m.md.AMsg(e.Message); // Display error msg
                Act.m.console.PipeButton.IsChecked = false;
            }
        }).Start();
        return true;
    }

    public bool RaisePipe()
    {
        TextBlock tb = Act.m.tb;
        try
        {
            pipe = new NamedPipeServerStream("PIPE1", PipeDirection.InOut, numThreads);
            threadId = Thread.CurrentThread.ManagedThreadId;
            pipe.WaitForConnection();
            Act.m.md.Msg("Pipe Raised");
            return true;
        }
        catch (Exception e)
        {
            string err = e.Message;
            tb.Inlines.Add(new Run("Pipe Failed to Init on Server Side"));
            tb.Inlines.Add(new LineBreak());
            return false;
        }
    }

    public void ServerWaitForMessages()
    {
        new Thread(() =>
        {
            cmd = "";
            ss = new StreamString(pipe);
            while (cmd != "CLOSE")
            {
                try
                {
                    buffer = new char[256];
                    text = "";
                    msg = ss.ReadString().ToUpper();
                    word = msg.Split(' ');
                    cmd = word[0].ToUpper();
                    for (i = 1; i < word.Length; i++) text += word[i] + " ";
                    switch (cmd)
                    {
                        case "AUTHENTICATE": ss.WriteString("I am PIPE1 server"); break;
                        case "SOMEPIPEREQUEST":ss.WriteString(doSomePipeRequestReturningString()):break;
                        case "CLOSE": ss.WriteString("CLOSE");// reply to client
                            Thread.Sleep(1000);// wait for client to pick-up shutdown message
                            pipe.Close();
                            Act.m.md.Msg("Server Shutdown ok"); // Server side message
                            break;
                    }
                }
                catch (IOException iox)
                {
                    string error = iox.Message;
                    Act.m.md.Msg(error);
                    break;
                }
            }
        }).Start();
    }

    public void DummyClientCloseServerRequest()
    {
        StreamString ss = new StreamString(dummyclient);
        ss.WriteString("CLOSE");
        ss.ReadString();
    }

//Usage, Place ToggleButtons inside StackPanel, and back them in code thus:

private void PipeButton_Checked(object sender, RoutedEventArgs e)
    {
        Act.m.pwin.ConnectDummyClient();
        Act.m.pwin.RaisePipe();
    }
private void PipeButton_Unchecked(object sender, RoutedEventArgs e)
    {
        Act.m.pwin.DummyClientCloseServerRequest();
        Act.m.console.WaitButton.IsChecked = false;
        Keyboard.Focus(Act.m.md.tb1);
    }
private void WaitButton_Checked(object sender, RoutedEventArgs e)
    {
        Act.m.pwin.Wait();
    }
private void WaitButton_Unchecked(object sender, RoutedEventArgs e)
    {
    }

//Worked like a charm for me. Respectfully, zzzbc }

Ceto answered 7/9, 2016 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.