Qual è un buon modo per chiudere i thread bloccati su NamedPipeServer # WaitForConnection?
-
03-07-2019 - |
Domanda
Avvio la mia applicazione che genera un numero di thread, ognuno dei quali crea un NamedPipeServer (.net 3.5 ha aggiunto tipi gestiti per Named Pipe IPC) e attende che i client si connettano (Blocks). Il codice funziona come 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);
....
Ora ho anche bisogno di un metodo di spegnimento per mettere in ordine questo processo. Ho provato il solito bool flag isShutdownRequested trick. Ma il pipestream rimane bloccato sulla chiamata WaitForConnection () e il thread non muore.
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!");
}
}
L'unione non ritorna mai.
Un'opzione che non ho provato ma che potrebbe funzionare è quella di chiamare Thread.Abort e recuperare l'eccezione. Ma non sembra giusto .. Eventuali suggerimenti
Aggiornamento 22/12/2009
Ci scusiamo per non averlo pubblicato prima. Questo è quello che ho ricevuto come risposta da Kim Hamilton (team BCL)
Il " giusto " modo di fare un interrompibile WaitForConnection deve chiamare BeginWaitForConnection, gestisci il nuovo connessione nel callback e chiusura il flusso di pipe per smettere di aspettare connessioni. Se il tubo è chiuso, EndWaitForConnection genererà ObjectDisposedException che il il thread di callback può catturare, ripulire eventuali estremità libere e uscire in modo pulito.
Ci rendiamo conto che questo deve essere un comune domanda, quindi qualcuno nella mia squadra lo è sto pianificando di blog presto su questo.
Soluzione
Passa alla versione asincrona: BeginWaitForConnection
.
Se viene mai completato, avrai bisogno di un flag in modo che il gestore del completamento possa semplicemente chiamare EndWaitForConnection
assorbendo eventuali eccezioni ed uscendo (chiama End ... per garantire che tutte le risorse possano essere pulite up).
Altri suggerimenti
Questo è di cattivo gusto, ma è l'unico metodo con cui ho avuto modo di lavorare. Crea un client "falso" e connettiti alla tua pipe denominata per passare oltre WaitForConnection. Funziona ogni volta.
Inoltre, anche Thread.Abort () non ha risolto questo problema per me.
_pipeserver.Dispose();
_pipeserver = null;
using (NamedPipeClientStream npcs = new NamedPipeClientStream("pipename"))
{
npcs.Connect(100);
}
È possibile utilizzare il seguente metodo di estensione. Si noti l'inclusione di 'ManualResetEvent cancelEvent': è possibile impostare questo evento da un altro thread per segnalare che il metodo di connessione in attesa dovrebbe interrompersi ora e chiudere la pipe. Includi cancelEvent.Set () quando si imposta m_bShutdownRequested e l'arresto dovrebbe essere relativamente grazioso.
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
}
Ho scritto questo metodo di estensione per risolvere questo 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
}
Un modo che potrebbe funzionare è il controllo di m_bShutdownRequested subito dopo WaitForConnection.
Durante il processo di spegnimento impostare il valore bool. Dopodiché invia messaggi fittizi a tutti i tubi esistenti in modo che aprano la connessione e controllino il bool e si chiudano in modo pulito.
Una soluzione più semplice e facile è creare un client fittizio e stabilire una connessione con il 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();
}
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, posiziona i pulsanti Toggle all'interno di StackPanel e esegui il backup nel codice in questo modo:
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)
{
}
// Ha funzionato come un incantesimo per me. Rispettivamente, zzzbc }