Winform 子线程影响到主线程运行的奇怪问题

morose999 2011-07-29 10:49:42
很简单的一个应用。主窗体启动后,启动一个子线程,这个子线程有个timer,每隔1.5秒来检测一个USB设备。但现在发现在1.5秒执行到检测到这个设备时,主窗体的一个内容滚动条会明显卡一下,下面看下代码


[主窗体启动线程]
            WatchKey wk = new WatchKey(this);
Thread thKey = new Thread(new ThreadStart(wk.StartWatch));
thKey.IsBackground = true;
thKey.Start();



主窗体有一个Richtextbox,里面是用来读取USB设备的相关信息的,比如ID等


[子线程类代码]


//子线程中的timer,现在设置的是每隔1.5秒就去读取一次USB设备中的信息
void t_Elapsed(object sender, ElapsedEventArgs e)
{
tempKeyID = EPass.GetKeyID(); //读取USB设备的ID(USB本身ID,跟系统硬件ID无关)
if (!string.IsNullOrEmpty(tempKeyID.Trim()) && tempKeyID.IndexOf("0000") < 0)
{

//main是主窗体,在主窗体中用KeyID变量保存了USB设备ID值
if (main.KeyID != tempKeyID)
{
main.OnButton(true); //启用主窗体的几个按钮
main.SetKeyID(tempKeyID); //设置主窗体中的KeyID变量值为当前USB设备ID
main.ReadInfo(); //读取USB设备相关信息并在主窗体的RichtextBox中输出
main.SetThisText("KEY已连接"); //设置主窗体标题
//t.Stop();
}
}
else
{
//如果是此USB设备被拨下

main.OnButton(false); //禁用主窗体的几个按钮
main.KeyID = "";
main.SetThisText("KEY未连接");
}


}



子线程中的主要代码就是里面的timer的事件。我试过通过主窗体中的按钮不断读取设备信息,直至richtextbox出现垂直滚动条,然后拖动滚动条,就在1.5秒读取设备信息的时候(此USB设备有个信号灯,可以看出是否有在读取/写入数据).能感觉到手动条明显卡了一下。但是如果设备中途不拨下或者换设备的话,timer中的其他代码只执行了一次,就是在插入的时候。但1.5秒还是会读取一下设备ID的,因为我要监测设备是否被拨下。

后来我把上面子线程timer事件中的t.stop()注释去掉,就是只执行一次,然后再拖动滚动条,就没有卡的感觉了,所以我分析是因为1.5秒读取设备信息的时候出现的问题。但并不清楚是怎么造成的


我把上面子线程中用到的主窗体中的委托代码贴出来,大家帮我分析一下


/// <summary>
/// 启用/禁用操作按钮
/// </summary>
/// <param name="on"></param>
public void OnButton(bool on)
{
if (this.InvokeRequired)
{
OnButtonCallBack ocb = new OnButtonCallBack(OnButton);
this.Invoke(ocb, new object[] { on });
}
else
{
if (on)
{
btnWrite.Enabled = true;
btnRead.Enabled = true;
btnDelete.Enabled = true;
}
else
{
btnWrite.Enabled = false;
btnRead.Enabled = false;
btnDelete.Enabled = false;
}
}
}


/// <summary>
/// 设置KEY ID值.供委托调用
/// </summary>
/// <param name="keyID"></param>
public void SetKeyID(string keyID)
{
if (this.InvokeRequired)
{
SetKeyIDInvoke sk = new SetKeyIDInvoke(SetKeyID);
this.Invoke(sk, new object[] { keyID });
}
else
{
this.KeyID = keyID;
}
}


/// <summary>
/// 读取Key信息
/// </summary>
public void ReadInfo()
{
if (this.KeyID.Trim() != "" && this.KeyID.IndexOf("0000") < 0)
{

object[] obj = GetKeyInfo(this.KeyID); //根据keyid获取相关信息

if (obj[0] != null)
{

//WriteString是用来向Richtextbox输出字符的,用AppendText
WriteString("\n操作时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
WriteString("\n硬件标识:" + this.KeyID);

WriteString("\n店铺名称:" + GetAgentName(Convert.ToInt16(obj[0])));
WriteString("\n分店名称:" + GetChildName(Convert.ToInt16(obj[1])));
WriteString("\n修改时间:" + obj[2].ToString());
WriteString("\n" + new string('=', 49));
}

}
}
...全文
614 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
康少_小贱 2011-08-03
  • 打赏
  • 举报
回复
感觉又有timer和委托异步等等
康少_小贱 2011-08-03
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 sp1234 的回复:]
你就说明t_Elapsed这个方法如何才被调用,这其实就够了。描述过多其它无关的只会混淆。
[/Quote]

同意
morose999 2011-08-03
  • 打赏
  • 举报
回复
OK.已经解决了

        /// <summary>
/// 读取KEY硬件ID
/// </summary>
/// <returns></returns>
public string GetKeyID()
{
string key = EPass.GetStrProperty(7, 0, "").ToString().Trim();
if (key.IndexOf("0000") >= 0)
{
try
{

EPass.OpenDevice(1, "");
object obj = EPass.GetStrProperty(7, 0, "");

if (!string.IsNullOrEmpty(obj.ToString().Trim()))
{
key=obj.ToString().Trim();
}
else
{
key="";
}
}
catch
{
key="";
}
}
return key;
}

造成卡的原因是OpenDevice这句,加了判断每个设备只是执行一次OpenDevice操作就可以了.不过为什么在子线程读取设备还是会造成主线程卡的原因还是没找出来
morose999 2011-08-01
  • 打赏
  • 举报
回复
UP~MARK
hen_ai_hen_ai_ni 2011-07-31
  • 打赏
  • 举报
回复
我觉得画面启动的时候,不需要一个独立的Thread去启动,直接启动timer即可
鸭梨山大帝 2011-07-31
  • 打赏
  • 举报
回复
tempKeyID = EPass.GetKeyID();

调试进去,查看一下EPass是否为null 或者 EPass.GetKeyID();是否会报错。
morose999 2011-07-31
  • 打赏
  • 举报
回复
UP求解
ianam 2011-07-31
  • 打赏
  • 举报
回复
mark
morose999 2011-07-31
  • 打赏
  • 举报
回复
我是在主窗体启动后就启动一个子线程,子线程的代码我是写在WatchKey类里面的

    class WatchKey
{
private static Main main;
private EPass1000ND EPass = new EPass1000ND();
Timer t = new Timer(1500);
private string tempKeyID = "";

public WatchKey(Main m)
{
main = m;

}

public void StartWatch()
{
t.AutoReset = true;
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
t.Start();
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{

tempKeyID = EPass.GetKeyID();
if (!string.IsNullOrEmpty(tempKeyID.Trim()) && tempKeyID.IndexOf("0000") < 0)
{
if (main.KeyID != tempKeyID)
{
main.OnButton(true);
main.SetKeyID(tempKeyID);
main.ReadInfo();
main.SetThisText("KEY已连接");
//t.Stop();
}
}
else
{
main.OnButton(false);
main.KeyID = "";
main.SetThisText("KEY未连接");
}


}
}

[Quote=引用 9 楼 lost_painting 的回复:]
tempKeyID = EPass.GetKeyID();

调试进去,查看一下EPass是否为null 或者 EPass.GetKeyID();是否会报错。
[/Quote]

经过测试,可以确认卡的原因就是出现在这里.我把Epass.GetKeyID的代码贴出来


/// <summary>
/// 读取KEY硬件ID
/// </summary>
/// <returns></returns>
public string GetKeyID()
{
try
{

EPass.OpenDevice(1, ""); //打开设备,只有打开设备后才能进行下一步操作

object obj = EPass.GetStrProperty(7, 0, ""); //获取设备ID
//EPass.CloseDevice();
if (!string.IsNullOrEmpty(obj.ToString().Trim()))
{
return obj.ToString().Trim();
}
else
{
return "";
}
}
catch
{
//没有插入设备
return "";
}
}


就是里面的OpenDevice打开设备这里.我是通过1.5秒来尝试打开这个设备,来监测此设备是否有插入或者是另外一个不同ID的设备插进去~
  • 打赏
  • 举报
回复
我给你找来msdn中关于timers.timer的描述,请你参考一下:
http://msdn.microsoft.com/zh-cn/library/system.timers.timer(v=VS.90).aspx
http://msdn.microsoft.com/zh-cn/library/system.timers.timer.synchronizingobject(v=VS.90).aspx


注意其中有这样一段话:
如果在 Windows 窗体设计器的 Visual Studio 中使用 Timer,则 SynchronizingObject 自动设置为包含 Timer 的控件。例如,如果将 Timer 放在 Form1(它从 Form 继承)的设计器上,则 Timer 的 SynchronizingObject 属性设置为 Form1 的实例。

也就是说,如果你是直接从工具箱拖入空间到窗体,那么它就是在窗体线程来执行Elapsed,因为这个参数自动设置为窗体对象。只有当你在代码中创建这个timer实例,并且不为其设置这个属性时,它才是保证自己去找一个系统(子)线程来执行的Elapsed的。
  • 打赏
  • 举报
回复
你就说明t_Elapsed这个方法如何才被调用,这其实就够了。描述过多其它无关的只会混淆。
  • 打赏
  • 举报
回复
[Quote=引用楼主 morose999 的回复:]
很简单的一个应用。主窗体启动后,启动一个子线程,这个子线程有个timer,每隔1.5秒来检测.......[/Quote]

子线程有个timer?我没有在你的描述中看懂你的“子线程中”怎么就有个timer了?

你写的“子线程类代码”这个概念就更加难懂了。没有什么“子线程类”概念。子线程调用的方法跟主线程调用的方法都是同样的方法,并没有单独区分某个方法只有子线程可以调用。区别是去调用方法(即你的t_Elapsed方法)的方法的不同,而不是它们写在某个类型就成了子线程了。


最后我给你说个我认为最靠谱的“结论”:你的t_Elapsed其实是在主线程中被调用的。
wind4fly 2011-07-31
  • 打赏
  • 举报
回复
子线程里面While循环使用Timer?这样子可能会产生多个Timer。考虑去掉子线程,或者吧Timer的启动直接放主线程。

morose999 2011-07-30
  • 打赏
  • 举报
回复
试过在
if (main.KeyID != tempKeyID)
{
main.OnButton(true); //启用主窗体的几个按钮
main.SetKeyID(tempKeyID); //设置主窗体中的KeyID变量值为当前USB设备ID
//main.ReadInfo(); //读取USB设备相关信息并在主窗体的RichtextBox中输出
//main.SetThisText("KEY已连接"); //设置主窗体标题
System.Windows.Forms.MessageBox.Show("我被执行了");
}


执行,也只是第一个1.5秒会弹出MessageBox.所以问题并不出现在这里.而是在
tempKeyID = EPass.GetKeyID();
这句里面的
morose999 2011-07-30
  • 打赏
  • 举报
回复
这个子线程只是第一次向主线程的控件赋值.


if (main.KeyID != tempKeyID)
{
main.OnButton(true); //启用主窗体的几个按钮
main.SetKeyID(tempKeyID); //设置主窗体中的KeyID变量值为当前USB设备ID
main.ReadInfo(); //读取USB设备相关信息并在主窗体的RichtextBox中输出
main.SetThisText("KEY已连接"); //设置主窗体标题
//t.Stop();
}



上面是在第一次插入设备的时候,主窗体的KeyID变量默认是空的,插入设备后,把设备ID传给KeyID,但在第二个1.5秒,只是执行到这句
tempKeyID = EPass.GetKeyID();       //读取USB设备的ID(USB本身ID,跟系统硬件ID无关)


并不会再向主窗体赋值跟向RichtextBox输出信息的
mjp1234airen4385 2011-07-30
  • 打赏
  • 举报
回复
你把向主线控件赋值的去掉就好了。
然后使用给主线程发消息的方式,这样的话,消息发出就好了。
主线程什么时候能处理完,就是主线程的事情了。
你看看这个办法能不能避免你的问题。
mjp1234airen4385 2011-07-30
  • 打赏
  • 举报
回复
因为你在子线程中向主线程的控件复制,这个过程就会造成主线程的卡死现象了。
morose999 2011-07-30
  • 打赏
  • 举报
回复
是同步的.而且这个WriteString也只是在刚插入的时候调用过一次而已.以后的每1.5秒,只有在子线程中执行了读取设备信息,但并没有在主窗体中进行操作
langmafeng 2011-07-30
  • 打赏
  • 举报
回复
WriteString方法是异步实现的吗?

110,545

社区成员

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

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

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