线程池的线程好像用完了

yang1216 2015-05-31 10:30:34
我的程序是用tcpclient与几十台下位机通讯。在论坛大神的帮助下,程序写成了这个样子。

private void frmMain_Load(object sender, EventArgs e)
{
proc();
}
private void proc()
{
timer = new System.Threading.Timer(h =>
{
if (enableComm)
{
foreach (var s in Session.Stations)
{
var station = Session.Stations.Find(p => p.Id == s.Id);
if (station.IsWorking == true)
{
if (!comm.COMMStates.ContainsKey(station.Id))
{
comm.COMMStates[station.Id] = new COMMState()
{
Station = station,
Data = new byte[250],
StationErrNum = 0,
Exception = null,
TcpClient = null
};
}
ThreadPool.QueueUserWorkItem(x => ThreadPollCallback(comm.COMMStates[station.Id]));
}
}
}

},null,0,1000);

}
public void ThreadPollCallback(object state)
{
var cs = state as COMMState;
if (cs == null) return;
var station = cs.Station;

//防止在connect 过程中再次连接,因为下位机只允许3个连接
if (cs.COMMUNICATION_STATE == COMMUNICATIONSTATE.连接&&cs.TriesNum>=3)
{
lock (lockObj)
{
cs.Exception = new Exception("communication timeout");
}
return;
}
cs.Thread = Thread.CurrentThread;
try
{
//连接
if (cs.TcpClient == null || !cs.TcpClient.Connected)
{
lock (lockObj)
{
cs.COMMUNICATION_STATE = COMMUNICATIONSTATE.连接;
cs.TriesNum++;
}
cs.TcpClient = new TcpClient(cs.Station.IP, 502);
}
TcpClient tc = cs.TcpClient;
lock (lockObj)
{
cs.COMMUNICATION_STATE = COMMUNICATIONSTATE.开始;
}
//读数据,实际就是write之后read
ModbusIpMaster master = ModbusIpMaster.CreateIp(tc);
ushort startAddress = 0;
ushort numInputs = 125;
ushort[] res = master.ReadHoldingRegisters(startAddress, numInputs);
lock (lockObj)
{
//处理数据,保存到数据库
cs.COMMUNICATION_STATE = COMMUNICATIONSTATE.完成;
}
}
catch (Exception e)
{
cs.COMMUNICATION_STATE = COMMUNICATIONSTATE.故障;
}
finally
{

}
}

现在的问题是,程序运行一个多小时后,通讯就会中断,断点发现这时候程序根本就不执行ThreadPollCallback,而且整个程序变得非常慢,感觉就像是线程池已经用完了,再有新的线程也不理会一样。
我猜想可能与tcp连接失败有关。
因为下位机中经常有一些没有连到网络,所以有一些线程是在不断重复“connect,失败”的过程。看上去这个过程每秒都要新开一个线程来做,而且旧的线程并不会被回收。
求助,这个问题怎么办啊?
...全文
697 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
yang1216 2015-06-01
  • 打赏
  • 举报
回复
引用 19 楼 LargeSkyMensk 的回复:
我发现编写程序容易出问题的人,往往在逻辑思维能力也表现一般。 要是让李大嘴来看这个程序,他肯定会说:这可真是一团遭。 就你这个功能,只需要开一个线程,根据实际情况,确定与设备的超时时间,在一个线程内处理所有设备请求,就可以了。 为什么只需要一个线程了? 你这个功能,不需要复杂的运算,只是简单的处理,对于现在的计算机来说,使用其中一个核心,足够了。
只用一个线程就是同步来做了吧,我试过,时间不够。
  • 打赏
  • 举报
回复
实际上,不应该在一个定时器中响应中去 foreach (var s in Session.Stations),因为这样比较乱,容易错误决定操作方式。 应该让每一个 Session 自己管理自己的定时轮询。外部的程序只要管理每一个 Session 的操作的 Run、Stop 就行了。
  • 打赏
  • 举报
回复
另外如果你的所谓“1秒钟”不是指静态固定的时间间隔,而是指“上一次通讯完毕之后(或者开始之后)秒钟,那么你更加不应该简单地不断定时轮询了。 在对某个下位机x操作完毕之后,它自己启动一个定时器,下一秒钟启动下一次轮询。跟其它下位机合适开始下一次操作没有关系。不要”统一开始操作“。 .net的定时器也很高效,你创建上万个定时器也没有关系。没有必要只用一个定时器。你应该尽量减少线程数,不要滥用线程。
  • 打赏
  • 举报
回复
你可以为你的程序加入几行“计数”代码,看看你的程序1个小时后的并发任务数是不是比你的下位机数据量还多“几十倍”。 定时执行任务是可以的,但是下一次定时的Interval应该是根据上一次任务所花的时间来动态调整的(1秒钟-上一个任务花掉的时间)。或者至少你应该避免对象同的下位机的操作“重入”。 另外,更重要地是,尽量不要陷入“轮询”这么低级的设计境地。尽量让你的程序响应事件,而不是轮询。
largeskymengsk 2015-06-01
  • 打赏
  • 举报
回复
我发现编写程序容易出问题的人,往往在逻辑思维能力也表现一般。 要是让李大嘴来看这个程序,他肯定会说:这可真是一团遭。 就你这个功能,只需要开一个线程,根据实际情况,确定与设备的超时时间,在一个线程内处理所有设备请求,就可以了。 为什么只需要一个线程了? 你这个功能,不需要复杂的运算,只是简单的处理,对于现在的计算机来说,使用其中一个核心,足够了。
於黾 2015-06-01
  • 打赏
  • 举报
回复
设置ReceiveTimeout之后,如果超时了还没有数据到达,就会报个错误,进入catch
yang1216 2015-06-01
  • 打赏
  • 举报
回复
引用 16 楼 Z65443344 的回复:
[quote=引用 15 楼 yang1216 的回复:] 防止重入也有问题,因为操作实际上是write之后等回复,现在就是write完之后read,如果下位机不回复就会一直等着。这时候只好重入一下,再write一次试试。
设置超时时间啊,超时了还没有回复,就别等了 我多次用收快递来打比方. 你从网上订购东西,就相当于是发送请求 你要接收数据,就好比是等快递. 等快递有很多种办法, 1.异步的方式就是快递来了放门卫,门卫给你打电话,然后你去取. 当然快递一直不来,门卫就一直不给你打电话,你需要自己设置个定时器,判断时间太长了怎么还没到,然后自己打电话去催. 2.同步的方式就是你订货之后,就没事去门卫那里溜达溜达,看看货到是没到. 而你现在就是用同步的思想来做. 同步的思想再加上线程池的概念,就是你自己不去看,而是派个人去看. 而你没告诉派去的人要等多长时间还不来就回来,而是没来就一直在那里等. 然后你又在时间过长之后,又派新的人去看. 导致门卫那里堵了一大堆人都在帮你等快递,这有任何意义??[/quote] 谢谢回复。道理明白了,但是怎么写不会。 直接设置ReceiveTimeout?我试过,好像不管用啊。
於黾 2015-06-01
  • 打赏
  • 举报
回复
引用 15 楼 yang1216 的回复:
防止重入也有问题,因为操作实际上是write之后等回复,现在就是write完之后read,如果下位机不回复就会一直等着。这时候只好重入一下,再write一次试试。
设置超时时间啊,超时了还没有回复,就别等了 我多次用收快递来打比方. 你从网上订购东西,就相当于是发送请求 你要接收数据,就好比是等快递. 等快递有很多种办法, 1.异步的方式就是快递来了放门卫,门卫给你打电话,然后你去取. 当然快递一直不来,门卫就一直不给你打电话,你需要自己设置个定时器,判断时间太长了怎么还没到,然后自己打电话去催. 2.同步的方式就是你订货之后,就没事去门卫那里溜达溜达,看看货到是没到. 而你现在就是用同步的思想来做. 同步的思想再加上线程池的概念,就是你自己不去看,而是派个人去看. 而你没告诉派去的人要等多长时间还不来就回来,而是没来就一直在那里等. 然后你又在时间过长之后,又派新的人去看. 导致门卫那里堵了一大堆人都在帮你等快递,这有任何意义??
yang1216 2015-06-01
  • 打赏
  • 举报
回复
引用 2 楼 sp1234 的回复:
你可以为你的程序加入几行“计数”代码,看看你的程序1个小时后的并发任务数是不是比你的下位机数据量还多“几十倍”。 定时执行任务是可以的,但是下一次定时的Interval应该是根据上一次任务所花的时间来动态调整的(1秒钟-上一个任务花掉的时间)。或者至少你应该避免对象同的下位机的操作“重入”。 另外,更重要地是,尽量不要陷入“轮询”这么低级的设计境地。尽量让你的程序响应事件,而不是轮询。
防止重入也有问题,因为操作实际上是write之后等回复,现在就是write完之后read,如果下位机不回复就会一直等着。这时候只好重入一下,再write一次试试。
於黾 2015-06-01
  • 打赏
  • 举报
回复
引用 13 楼 yang1216 的回复:
但现在我肯定没做死循环,线程池还是用完了
没做死循环,你线程里又不断的调用新线程,无限递归,其实还是相当于死循环,可能还不如死循环
yang1216 2015-06-01
  • 打赏
  • 举报
回复
但现在我肯定没做死循环,线程池还是用完了
於黾 2015-06-01
  • 打赏
  • 举报
回复
引用 10 楼 yang1216 的回复:
但是仍然想问一下,ThreadPool.QueueUserWorkItem的线程怎么才能被回收呢?强制abort一下?
你只需要保证线程池里的线程会执行完退出,而不是在里面死循环,就可以了
  • 打赏
  • 举报
回复
ThreadPool自身会管理线程,不需要去控制
yang1216 2015-06-01
  • 打赏
  • 举报
回复
但是仍然想问一下,ThreadPool.QueueUserWorkItem的线程怎么才能被回收呢?强制abort一下?
yang1216 2015-06-01
  • 打赏
  • 举报
回复
引用 8 楼 starfd 的回复:
[quote=引用 6 楼 yang1216 的回复:] [quote=引用 1 楼 starfd 的回复:] ThreadPool.QueueUserWorkItem本身就是返回一个bool值的,返回true代表分配成功 重复connect失败,开新线程也还是这样而已……
为了保证某个时候某台下位机恢复网络后能马上开始通讯,所以每次都要试着去连一下,哪怕重复失败。[/quote] 就你这一句话,就知道你的线程池为啥满了…… 你为啥要开新线程去判断是否能通讯呢?为什么不能再fail后再此线程内继续连接呢?[/quote] 茅塞顿开,我去试试
  • 打赏
  • 举报
回复
引用 6 楼 yang1216 的回复:
[quote=引用 1 楼 starfd 的回复:] ThreadPool.QueueUserWorkItem本身就是返回一个bool值的,返回true代表分配成功 重复connect失败,开新线程也还是这样而已……
为了保证某个时候某台下位机恢复网络后能马上开始通讯,所以每次都要试着去连一下,哪怕重复失败。[/quote] 就你这一句话,就知道你的线程池为啥满了…… 你为啥要开新线程去判断是否能通讯呢?为什么不能再fail后再此线程内继续连接呢?
yang1216 2015-06-01
  • 打赏
  • 举报
回复
引用 4 楼 sp1234 的回复:
实际上,不应该在一个定时器中响应中去 foreach (var s in Session.Stations),因为这样比较乱,容易错误决定操作方式。 应该让每一个 Session 自己管理自己的定时轮询。外部的程序只要管理每一个 Session 的操作的 Run、Stop 就行了。
我这是个winform程序,只是把全局变量起名叫Session而已,对不起啊。
yang1216 2015-06-01
  • 打赏
  • 举报
回复
引用 1 楼 starfd 的回复:
ThreadPool.QueueUserWorkItem本身就是返回一个bool值的,返回true代表分配成功 重复connect失败,开新线程也还是这样而已……
为了保证某个时候某台下位机恢复网络后能马上开始通讯,所以每次都要试着去连一下,哪怕重复失败。
yang1216 2015-06-01
  • 打赏
  • 举报
回复
引用 2 楼 sp1234 的回复:
你可以为你的程序加入几行“计数”代码,看看你的程序1个小时后的并发任务数是不是比你的下位机数据量还多“几十倍”。 定时执行任务是可以的,但是下一次定时的Interval应该是根据上一次任务所花的时间来动态调整的(1秒钟-上一个任务花掉的时间)。或者至少你应该避免对象同的下位机的操作“重入”。 另外,更重要地是,尽量不要陷入“轮询”这么低级的设计境地。尽量让你的程序响应事件,而不是轮询。
我写了个Debug.WriteLine(string.Format("通讯线程开始 {0}", Thread.CurrentThread.GetHashCode().ToString()); 然后看着线程号从两位数跑到4位数。。。 我的需求就是每秒钟轮询所有下位机获得数据保存。所以必须是每秒都轮询啊,不能动态调整。
  • 打赏
  • 举报
回复
ThreadPool.QueueUserWorkItem本身就是返回一个bool值的,返回true代表分配成功 重复connect失败,开新线程也还是这样而已……

110,539

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧