Каков хороший способ отключить потоки, заблокированные на NamedPipeServer#WaitForConnection?
-
03-07-2019 - |
Вопрос
Я запускаю свое приложение, которое порождает несколько потоков, каждый из которых создает NamedPipeServer (в .net 3.5 добавлены управляемые типы для IPC именованного канала) и ждет подключения клиентов (блокирует).Код работает так, как задумано.
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);
....
Теперь мне также нужен метод завершения работы, чтобы аккуратно завершить этот процесс.Я попробовал обычный трюк с логическим флагом isShutdownRequested.Но поток канала остается заблокированным при вызове WaitForConnection(), и поток не завершается.
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!");
}
}
Регистрация никогда не возвращается.
Вариант, который я не пробовал, но, возможно, сработает, — это вызвать Thread.Abort и съесть исключение.Но это неправильно..Какие-либо предложения
Обновление 22 декабря 2009 г.
Извините, что не опубликовала это раньше..Вот что я получил в ответ от Кима Хэмилтона (команда BCL):
«Правильный» способ сделать прерывистый waitforconnection - это вызов BegnwaitForConnection, обработать новое соединение в обратном вызове и закрыть поток труб, чтобы перестать ждать соединений.Если труба закрыта, EndwaitforConnection будет выбросить ObjectDisposedException, которое может поймать нить обратного вызова, очистить любые свободные концы и выходить из чистого.
Мы понимаем, что это должен быть общий вопрос, поэтому кто -то из моей команды планирует в ближайшее время в блоге об этом.
Решение
Переключитесь на асинхронную версию: BeginWaitForConnection
.
Если оно когда-либо завершится, вам понадобится флаг, чтобы обработчик завершения мог просто вызвать EndWaitForConnection
поглощение любых исключений и выход (вызов End...чтобы гарантировать возможность очистки любых ресурсов).
Другие советы
Это банально, но это единственный метод, который мне удалось применить.Создайте «поддельный» клиент и подключитесь к именованному каналу, чтобы пройти мимо WaitForConnection.Работает каждый раз.
Кроме того, даже Thread.Abort() не решил для меня эту проблему.
_pipeserver.Dispose();
_pipeserver = null;
using (NamedPipeClientStream npcs = new NamedPipeClientStream("pipename"))
{
npcs.Connect(100);
}
Вы можете использовать следующий метод расширения.Обратите внимание на включение «ManualResetEvent cancelEvent» — вы можете установить это событие из другого потока, чтобы сигнализировать о том, что ожидающий метод подключения должен прервать сейчас и закрыть канал.Включите cancelEvent.Set() при настройке m_bShutdownRequested, и завершение работы должно быть относительно плавным.
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
}
Я написал этот метод расширения, чтобы решить эту проблему:
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
}
Один из способов, который может сработать, — это проверка m_bShutdownRequested сразу после WaitForConnection.
Во время процесса выключения установите bool.После этого отправьте фиктивные сообщения всем существующим каналам, чтобы они открыли соединение, проверили bool и корректно завершили работу.
Самое простое и легкое решение — создать фиктивный клиент и установить соединение с сервером.
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();
}
//Использование, поместите ToggleButtons внутри StackPanel и подкрепите их кодом следующим образом:
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)
{
}
// Для меня это сработало как шарм.С уважением, zzzbc}