¿Cuál es una buena manera de cerrar los subprocesos bloqueados en NamedPipeServer # WaitForConnection?
-
03-07-2019 - |
Pregunta
Comienzo mi aplicación que genera una serie de subprocesos, cada uno de los cuales crea un NamedPipeServer (.net 3.5 agregados tipos administrados para Named Pipe IPC) y espera que los clientes se conecten (Bloques). El código funciona según lo previsto.
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);
....
Ahora también necesito un método de apagado para desactivar este proceso de manera limpia. Probé el truco habitual del indicador de bool isShutdownRequested. Pero el flujo de flujo permanece bloqueado en la llamada WaitForConnection () y el hilo no se muere.
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!");
}
}
Unirse nunca vuelve.
Una opción que no probé pero que posiblemente funcionaría es llamar a Thread.Abort y devorar la excepción. Pero no se siente bien ... Cualquier sugerencia
Actualización 2009-12-22
Lo siento por no haber publicado esto antes. Esto es lo que recibí como respuesta de Kim Hamilton (equipo de BCL)
El " derecha " forma de hacer un interrumpible WaitForConnection es llamar BeginWaitForConnection, manejar el nuevo Conexión en el callback, y cierre. la corriente de la tubería para dejar de esperar conexiones Si el tubo está cerrado, EndWaitForConnection lanzará ObjectDisposedException que el hilo de devolución de llamada puede atrapar, limpiar cualquier cabos sueltos, y salga limpiamente.
Nos damos cuenta de que esto debe ser un común pregunta, entonces alguien en mi equipo es planeando hacer un blog sobre esto pronto.
Solución
Cambie a la versión asíncrona: BeginWaitForConnection
.
Si alguna vez se completa, necesitará un indicador para que el controlador de finalización pueda simplemente llamar a EndWaitForConnection
para absorber cualquier excepción y salir (llame a End ... para asegurarse de que se puedan limpiar los recursos) arriba).
Otros consejos
Esto es cursi, pero es el único método que he llegado a trabajar. Cree un cliente "falso" y conéctese a su canalización con nombre para moverse más allá de WaitForConnection. Funciona cada vez.
Además, incluso Thread.Abort () no solucionó este problema para mí.
_pipeserver.Dispose();
_pipeserver = null;
using (NamedPipeClientStream npcs = new NamedPipeClientStream("pipename"))
{
npcs.Connect(100);
}
Puedes usar el siguiente método de extensión. Tenga en cuenta la inclusión de 'ManualResetEvent cancelEvent': puede configurar este evento desde otro hilo para indicar que el método de conexión en espera debe abortarse ahora y cerrar la canalización. Incluya cancelEvent.Set () al configurar m_bShutdownRequested y el cierre debe ser relativamente elegante.
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
}
Escribí este método de extensión para resolver este problema:
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
}
Una forma en que podría funcionar es buscar m_bShutdownRequested justo después de WaitForConnection.
Durante el proceso de cierre, configure el bool. Después de eso, envíe mensajes falsos a todas las tuberías existentes para que abran la conexión y verifiquen el bool y se apaguen limpiamente.
Una solución más simple y sencilla es crear un cliente ficticio y hacer una conexión con el servidor.
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();
}
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();
}
// Uso, coloque ToggleButtons dentro de StackPanel, y haga una copia de seguridad en el código de esta manera:
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)
{
}
// Trabajó como un encanto para mí. Respetuosamente, zzzbc }