Tôi gặp sự cố với Monitor.Wait và Monitor.Pulse lồng vào nhau trong máy chủ TCP đa luồng. Để chứng minh vấn đề của tôi, đây là mã máy chủ của tôi:Monitor.Wait/Pulse điều kiện chủng tộc trong một máy chủ đa luồng
public class Server
{
TcpListener listener;
Object sync;
IHandler handler;
bool running;
public Server(IHandler handler, int port)
{
this.handler = handler;
IPAddress address = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
listener = new TcpListener(address, port);
sync = new Object();
running = false;
}
public void Start()
{
Thread thread = new Thread(ThreadStart);
thread.Start();
}
public void Stop()
{
lock (sync)
{
listener.Stop();
running = false;
Monitor.Pulse(sync);
}
}
void ThreadStart()
{
if (!running)
{
listener.Start();
running = true;
lock (sync)
{
while (running)
{
try
{
listener.BeginAcceptTcpClient(new AsyncCallback(Accept), listener);
Monitor.Wait(sync); // Release lock and wait for a pulse
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
}
void Accept(IAsyncResult result)
{
// Let the server continue listening
lock (sync)
{
Monitor.Pulse(sync);
}
if (running)
{
TcpListener listener = (TcpListener)result.AsyncState;
using (TcpClient client = listener.EndAcceptTcpClient(result))
{
handler.Handle(client.GetStream());
}
}
}
}
Và đây là mã khách hàng của tôi:
class Client
{
class EchoHandler : IHandler
{
public void Handle(Stream stream)
{
System.Console.Out.Write("Echo Handler: ");
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[1024];
int count = 0;
while ((count = stream.Read(buffer, 0, 1024)) > 0)
{
sb.Append(Encoding.ASCII.GetString(buffer, 0, count));
}
System.Console.Out.WriteLine(sb.ToString());
System.Console.Out.Flush();
}
}
static IPAddress localhost = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
public static int Main()
{
Server server1 = new Server(new EchoHandler(), 1000);
Server server2 = new Server(new EchoHandler(), 1001);
server1.Start();
server2.Start();
Console.WriteLine("Press return to test...");
Console.ReadLine();
// Note interleaved ports
SendMsg("Test1", 1000);
SendMsg("Test2", 1001);
SendMsg("Test3", 1000);
SendMsg("Test4", 1001);
SendMsg("Test5", 1000);
SendMsg("Test6", 1001);
SendMsg("Test7", 1000);
Console.WriteLine("Press return to terminate...");
Console.ReadLine();
server1.Stop();
server2.Stop();
return 0;
}
public static void SendMsg(String msg, int port)
{
IPEndPoint endPoint = new IPEndPoint(localhost, port);
byte[] buffer = Encoding.ASCII.GetBytes(msg);
using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
s.Connect(endPoint);
s.Send(buffer);
}
}
}
Khách hàng gửi Bảy bài viết, nhưng máy chủ chỉ in bốn:
Press return to test... Press return to terminate... Echo Handler: Test1 Echo Handler: Test3 Echo Handler: Test2 Echo Handler: Test4
Tôi nghi ngờ màn hình đang bị nhầm lẫn bằng cách cho phép Pulse
xảy ra (trong phương thức Accept
của máy chủ) trước khi xảy ra Wait
(i n phương pháp ThreadStart
), mặc dù ThreadStart
vẫn nên có khóa trên đối tượng sync
cho đến khi nó gọi Monitor.Wait()
và sau đó phương thức Accept
có thể lấy khóa và gửi Pulse
. Nếu bạn nhận xét ra hai dòng sau trong phương pháp Stop()
của máy chủ:
//listener.Stop();
//running = false;
Các thông điệp còn lại xuất hiện khi phương pháp Stop()
của máy chủ được gọi (ví dụ: thức dậy đối tượng sync
của máy chủ gây ra nó để gửi các tin nhắn gửi đến còn lại). Dường như với tôi điều này chỉ có thể xảy ra trong điều kiện đua giữa các phương pháp ThreadStart
và Accept
, nhưng khóa xung quanh đối tượng sync
sẽ ngăn điều này.
Bất kỳ ý tưởng nào?
Rất cám ơn, Simon.
ps. Lưu ý rằng tôi biết rằng đầu ra xuất hiện không đúng thứ tự vv, tôi đặc biệt hỏi về tình trạng cuộc đua giữa các khóa và Màn hình. Chúc mừng, SH.
Cảm ơn Mats. Tôi cho rằng BeginAcceptTcpClient luôn chạy trên một chuỗi riêng biệt và do đó tôi có thể sử dụng đối tượng đồng bộ hóa như một phần quan trọng. Bạn đã được tại chỗ trên và tín hiệu là con đường để đi. Cảm ơn một lần nữa. SH –