嗨,我在做我的项目时感到头疼。
摘要:
我有一个处理所有客户端的ClientHandler类。(网络工作者是主回路)
ClientList的实现如下:
public List<Client> ClientList { get; private set; }
每次我尝试访问ClientList (读/写)时,我都使用.
lock(ClientList){}
lock(ClientHandler.ClientList){}
..。取决于我在ClientHandler内外的位置。
到目前为止,我没有使用任何锁,因此存在一些并发问题。
但是现在,当我使用/误用锁的时候,我遇到了一些保持生命的问题。
如果客户端连接:
public bool AddClient(Client client)
{
lock (ClientList)
{
if (client == null || ClientList.Contains(client))
return false;
ClientList.Add(client);
return true;
}
}
每一秒我的计时器都会排起一条活的长队:
private void KeepAliveTimer_Elapsed(object sender, ElapsedEventArgs e)
{
KeepAliveTimer.Stop();
lock (ClientList)
{
if (ClientList.Count > 0)
{
foreach (Client client in ClientList)
{
lock (client)
{
client.PacketQueue.Enqueue(new KeepAlivePacket());
}
}
}
}
KeepAliveTimer.Start();
}
我现在的主回路是:
private void Networker()
{
while (IsRunning)
{
lock (ClientHandler.ClientList)
{
if (ClientHandler.ClientList.Count > 0)
{
foreach (Client client in ClientHandler.ClientList)
{
// Check if client has data to read.
// Read for up to 10 msecs.
if (client.DataAvailable)
{
DateTime expiry = DateTime.Now.AddMilliseconds(10);
while (DateTime.Now <= expiry)
{
int id = client.ReadByte();
if (id == -1 || !PacketHandler.HandlePacket((byte)id, client, this))
{
ClientHandler.DisconnectClient(client);
continue;
}
}
}
// Check if client has data to write.
// Write for up to 10 msecs.
if (client.PacketQueue.Count > 0)
{
DateTime expiry = DateTime.Now.AddMilliseconds(10);
while (DateTime.Now <= expiry && client.PacketQueue.Count > 0)
{
IPacket packet = client.PacketQueue.Dequeue();
if (!packet.Write(client))
{
ClientHandler.DisconnectClient(client);
continue;
}
}
}
}
}
}
Thread.Sleep(1);
}
}
在所有这些锁之前,我的测试客户端每秒钟都会得到一个KeepAlivePacket。
现在我只得到一次,因为在第一个KeepAlivePacket之后,KeepAliveTimer_Elapsed不能再访问锁了,因为它被其他线程永久锁定了(用一些调试输出对它进行了测试)。
在提供的代码中是否有什么东西可能是疯子,还是我做错了什么?
如果有人能帮我摆脱这种痛苦就太好了。
编辑(感谢Joachim Isaksson):
我不知道这是否是唯一的错误,但我忘记了一件事,就是在读取第一个包后,是否有可用的数据在主循环中签入。
这是第一个问题,因为我只用我的TestClient发送了一个包,而服务器因为事先没有检查而被困在client.ReadByte上。
if (client.DataAvailable)
{
DateTime expiry = DateTime.Now.AddMilliseconds(10);
while (DateTime.Now <= expiry && client.DataAvailable)
{
try
{
int id = client.ReadByte();
// do stuff...
}...
}
}
发布于 2013-12-22 18:32:08
为什么不使用System.collections.concurrent中的集合来代替自己的锁定呢?
发布于 2013-12-22 18:45:54
封装资源,并为每个共享资源使用单独的锁对象。锁定集合实例(共享资源)或其所有者实例被认为是错误的做法(!)。
然后,添加在拥有私有集合实例的对象上操作集合的所有必要方法。
readonly List<MyItemClass> _myItems = new List<MyItemClass>();
readonly object lockObject = new object();
public IEnumerable<MyItemClass> MyItems
{
get
{
lock(lockObject)
{
return _myItems.ToArray();
}
}
}
public void Add(MyItemClass item)
{
lock(lockObject)
{
_myItems.Add(item);
}
}
public bool Remove(MyItemClass item)
{
lock(lockObject)
{
return _myItems.Remove(item);
}
}
这让我省去了很多挫折。
关闭主题,但有些关联:如果您的集合是ObservableCollection,则可以对静态方法进行调用。
BindingOperations.EnableCollectionSynchronization(_myItems, lockObject)
在所有者实例构造函数中。这将确保即使在另一个线程上更新集合,即使WPF的绑定机制也可以枚举该集合。它在枚举列表时使用与Add和Remove方法相同的锁定对象(重绘项目列表控件等)。静态方法是.NET 4.5中的新方法,无需从ViewModel中向UI线程发送可观察的集合更改。
https://stackoverflow.com/questions/20732586
复制相似问题