大家来看一看这个神奇的问题(绝对,绝对神奇;连if else 都几乎被颠覆了),我实在是木有办法了(关于线程安全的)

_lee_chong 2013-01-25 03:20:29

public void SetProcessInfo(int value)
{
if (progressBar1.InvokeRequired)
{
Action<int> setProcessDelegate = new Action<int>(SetProcessInfo);
progressBar1.Invoke(setProcessDelegate, value);
}
else
{//此处有断点1,条件(!progressBar1.InvokeRequired)
if (value > progressBar1.Maximum)
value = progressBar1.Maximum;
if (value < progressBar1.Minimum)
value = progressBar1.Minimum;

progressBar1.Value = value;//此处有断点2,条件(progressBar1.InvokeRequired)
}
}

你难以想象断点2会被击中;一开始我以为是在上面if时 InvokeRequired 为false 然后在执行到断点2之间变成了 true;于是在断点1处设下断点 判断false的断点,断点2击中前断点1未被击中过;说明至少至少断点1的时候InvokeRequired为true了;
除非在if的时候InvokeRequired为false,然后到了断点1处变为了true...不然实在...
哎...谁了解的帮帮忙吧;这都出错让我怎么弄啊....当然还有个办法把线程检测那玩意关了
...全文
469 27 打赏 收藏 转发到动态 举报
写回复
用AI写文章
27 条回复
切换为时间正序
请发表友善的回复…
发表回复
_lee_chong 2013-01-28
  • 打赏
  • 举报
回复
引用 22 楼 sp1234 的回复:
引用 16 楼 wddw1986 的回复:期待楼主可以拿出一个干净的可测试例子,这样这个问题才有讨论的意义。 拿一段代码,然后一口咬定别的线程都不影响,我觉的没什么意思。比如 public class A { public int count = 0; } 有这么一段代码 void Func(A a) { if(a.count == 0) ……
这不存在哦,我那不是局部变量;整合起来说我的问题是,在句柄创建前做了不该做的事,直接原因上来说,属于线程同步问题,本质原因是我对系统机制有比较大的欠缺
_lee_chong 2013-01-28
  • 打赏
  • 举报
回复
引用 19 楼 wddw1986 的回复:
看看MSDN上写的 如果控件句柄尚不存在,则 InvokeRequired 沿控件的父级链搜索,直到它找到有窗口句柄的控件或窗体为止。 如果找不到合适的句柄, InvokeRequired 方法将返回 false。 这意味着如果不需要 Invoke(调用发生在同一线程上),或者如果控件是在另一个线程上创建的但尚未创建控件的句柄,则 InvokeRequi……
好吧,哥们你是对的,我那天这段话怎么看怎么没明白,今天一觉醒来,再看一遍msdn就清楚了... 问题原因是,虽然我在子线程启动前构造好了窗体及控件但是还没有创建好句柄;而我所谓的取一次值就正常,也是msdn上说的, 通过调用 Handle 属性强制创建句柄
linuxca 2013-01-28
  • 打赏
  • 举报
回复
感觉是多线程的问题.
  • 打赏
  • 举报
回复
这其实就好像是你第一次上街见到汽车,必定感觉新鲜。 lz不要太执拗,除了技术之外,更要尽快学程序员的“思考方法”,就好了。
  • 打赏
  • 举报
回复
引用 16 楼 wddw1986 的回复:
期待楼主可以拿出一个干净的可测试例子,这样这个问题才有讨论的意义。 拿一段代码,然后一口咬定别的线程都不影响,我觉的没什么意思。比如 public class A { public int count = 0; } 有这么一段代码 void Func(A a) { if(a.count == 0) { } el……
不是因为“另一个线程会改a变量,而是a变量此时根本就是有多个!每一个执行Func的线程都分别有一个自己的栈,都分别有一个a变量。而楼主根本不知道也不愿意去想想此a、彼a的区别。
  • 打赏
  • 举报
回复
引用 8 楼 lc316546079 的回复:
总之我认为这和线程影响无关,应该是framework在这方面的实现有问题,事实上我之前也出现过类似的问题; 当在主线程创建了线程时;在子线程直接判断InvokeRequired的值后总是执行到else里面;可是当我查看InvokeRequired的值时确总是true; 目前有个办法可以无语的解决这个问题,就是在else里访问主线程创建的控件之前对InvokeRequi……
不要瞎找理由了。多线程时你还要逐行中断调试,谁教你的? 找个人问问,不要太执拗。
cheng2005 2013-01-25
  • 打赏
  • 举报
回复
看看MSDN上写的 如果控件句柄尚不存在,则 InvokeRequired 沿控件的父级链搜索,直到它找到有窗口句柄的控件或窗体为止。 如果找不到合适的句柄, InvokeRequired 方法将返回 false。 这意味着如果不需要 Invoke(调用发生在同一线程上),或者如果控件是在另一个线程上创建的但尚未创建控件的句柄,则 InvokeRequired 可以返回 false。 如果尚未创建控件的句柄,您就不能简单地在控件上调用属性、方法或事件。 这可能导致在后台线程上创建控件的句柄,从而隔离不带消息泵的线程上的控件并使应用程序不稳定。 当 InvokeRequired 在后台线程上返回 false 时,您也可以通过检查 IsHandleCreated 的值来避免这种情况。 如果尚未创建控件句柄,您必须等到控件句柄已创建,才能调用 Invoke 或 BeginInvoke。 通常,仅当在应用程序主窗体的构造函数中创建了后台线程时(如同在 Application.Run(new MainForm()) 中),在已经显示窗体或取消 Application.Run 之前,才会发生这种情况。 一种解决方案是等到已经创建了窗体的句柄,然后启动后台线程。 通过调用 Handle 属性强制创建句柄,或者等待 Load 事件启动后台进程。 一种更好的解决方案是使用 SynchronizationContext 返回的 SynchronizationContext,而不是使用控件进行线程间封送处理。 简单起来就一句话,句柄创建之前InvokeRequired 属性其实没什么用,一直返回false。 而你用子线程去调了一次InitializeControl(),看看上面的红色文字,写的清清楚楚明明白白。 就是说你的判断先进了else,然后句柄创建成功了,这时当然就变成了true。 至于解决方案MSDN也告诉你了,有心学习就找点正经的资料看,别总是自己脑补。
_lee_chong 2013-01-25
  • 打赏
  • 举报
回复
唯一的疑点是,我的form是先构造好,再开子线程,再showdialog的

ProcessForm frmProcess = (ProcessForm)ApplicationTool.FormsManager.GetForm(FormTypes.ProcessForm);
            frmProcess.InitializeControl();//不确定子线程是否会在显示时,完成进度窗体的初始化,便手动初始化

            var tokenSource = new CancellationTokenSource();
            Task.Factory.StartNew(new Action<object>(ProgramStart), frmProcess, tokenSource.Token).ContinueWith(frmProcess.TaskOver);
            DialogResult result = (frmProcess).ShowDialog();
_lee_chong 2013-01-25
  • 打赏
  • 举报
回复
引用 14 楼 hdt 的回复:
没有重现你的错误,再贴些别的代码?

这是form的构造函数,为了启动速度,我在main里面调用构造函数(false)
然后在子线程再调用的InitializeControl();但是InitializeControl里也是有InvokeRequired判断
 #region 初始化
        private bool _isInitializeControl;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="isInitialize">是否设置窗体子控件及属性(false)</param>
        public ProcessForm(bool isInitialize = true)
        {
            if (isInitialize)
                InitializeControl();
        }
        /// <summary>
        /// 设置窗体子控件及属性
        /// </summary>
        public void InitializeControl()
        {
            if (!_isInitializeControl)
            {
                _isInitializeControl = true;
                if (this.InvokeRequired)
                {
                    this.Invoke(new Action(InitializeControl));
                }
                else
                {
                    this.Controls.Clear();
                    InitializeComponent();
                }
            }
        }
        #endregion
cheng2005 2013-01-25
  • 打赏
  • 举报
回复
期待楼主可以拿出一个干净的可测试例子,这样这个问题才有讨论的意义。 拿一段代码,然后一口咬定别的线程都不影响,我觉的没什么意思。比如 public class A { public int count = 0; } 有这么一段代码 void Func(A a) { if(a.count == 0) { } else { //这里a.count的值是有可能等于0的,因为另外一个线程可能会改 } }
真相重于对错 2013-01-25
  • 打赏
  • 举报
回复
清理你的项目,重新生成,简化你的代码,先把不相关注释掉,再调试
真相重于对错 2013-01-25
  • 打赏
  • 举报
回复
没有重现你的错误,再贴些别的代码?
_lee_chong 2013-01-25
  • 打赏
  • 举报
回复
同样是上面那段代码,唯一区别我加了个条件断点

public void SetProcessInfo(int value)
        {
            //System.Diagnostics.Debug.WriteLine("if:" + progressBar1.InvokeRequired);
            if (progressBar1.InvokeRequired)
            {
                System.Diagnostics.Debug.WriteLine("if true:" + progressBar1.InvokeRequired);

                Action<int> setProcessDelegate = new Action<int>(SetProcessInfo);
                progressBar1.Invoke(setProcessDelegate, value);
            }
            else
            {
                //System.Diagnostics.Debug.WriteLine("else start:" + progressBar1.InvokeRequired);
                if (value > progressBar1.Maximum)
                    value = progressBar1.Maximum;
                if (value < progressBar1.Minimum)
                    value = progressBar1.Minimum;

                //System.Diagnostics.Debug.WriteLine("else end:" + progressBar1.InvokeRequired);
                if (progressBar1.InvokeRequired)//断点1,条件(!progressBar1.InvokeRequired)
                {
                    System.Diagnostics.Debug.WriteLine("else end: true");
                }
                progressBar1.Value = value;//断点2,条件(progressBar1.InvokeRequired)
            }
        }
断点1未被击中直接击中的断点2,但是和上次没加断点1的区别是这次输出了 "else end: true" 神奇吧
_lee_chong 2013-01-25
  • 打赏
  • 举报
回复
引用 10 楼 phommy 的回复:
哦 又想起个疑点 如果你在构造函数或Load里调用过这个方法,发生奇怪的事也正常= = 可以推迟在Shown里调用。。不是这种情况就算了。。
如果你说的是我这个函数所在form的话,哪么不是的;这个函数所在的form我是在程序一启动就构造好了,然后才启动的子线程加载数据资源等
_lee_chong 2013-01-25
  • 打赏
  • 举报
回复
好吧,大家看看这版代码吧,我不得不告诉大家在击中断点2之前debug根本没输出"else end: true"

public void SetProcessInfo(int value)
        {
            //System.Diagnostics.Debug.WriteLine("if:" + progressBar1.InvokeRequired);
            if (progressBar1.InvokeRequired)
            {
                System.Diagnostics.Debug.WriteLine("if true:" + progressBar1.InvokeRequired);

                Action<int> setProcessDelegate = new Action<int>(SetProcessInfo);
                progressBar1.Invoke(setProcessDelegate, value);
            }
            else
            {
                //System.Diagnostics.Debug.WriteLine("else start:" + progressBar1.InvokeRequired);
                if (value > progressBar1.Maximum)
                    value = progressBar1.Maximum;
                if (value < progressBar1.Minimum)
                    value = progressBar1.Minimum;

                //System.Diagnostics.Debug.WriteLine("else end:" + progressBar1.InvokeRequired);
                if (progressBar1.InvokeRequired)
                {
                    System.Diagnostics.Debug.WriteLine("else end: true");
                }
                progressBar1.Value = value;//断点2,条件(progressBar1.InvokeRequired)
            }
        }
phommy 2013-01-25
  • 打赏
  • 举报
回复
哦 又想起个疑点 如果你在构造函数或Load里调用过这个方法,发生奇怪的事也正常= = 可以推迟在Shown里调用。。不是这种情况就算了。。
_lee_chong 2013-01-25
  • 打赏
  • 举报
回复
引用 6 楼 phommy 的回复:
还真没见过,你试试右键工具条把debug location调出来,f11单步看 有可能你f11一下跟踪就换别的线程了,经常会 另外watch中的表达式有时会有副作用,你看下有没有可疑的 然后是检查设置中debug下“暂停时暂停所有线程”是不是打勾了
感觉应该和其它线程无关; 这里除了我上面说的,想办法在给控件属性赋值前访问一次 InvokeRequired 外目前没有别的办法;我尝试在属性赋值前sleep 1秒也不行;我想应该是InvokeRequired的get里做了一定的处理;正准备进去看看
_lee_chong 2013-01-25
  • 打赏
  • 举报
回复
总之我认为这和线程影响无关,应该是framework在这方面的实现有问题,事实上我之前也出现过类似的问题; 当在主线程创建了线程时;在子线程直接判断InvokeRequired的值后总是执行到else里面;可是当我查看InvokeRequired的值时确总是true; 目前有个办法可以无语的解决这个问题,就是在else里访问主线程创建的控件之前对InvokeRequired做一次访问,比如我上面代码里注释掉的日志输出;这时候就能防止这种无语的问题发生;但是这种解决方式是很无稽的;
_lee_chong 2013-01-25
  • 打赏
  • 举报
回复
目前看来就这个可能: "if 判断后 到 下面 断点2这一瞬间progressBar1.InvokeRequired的值被别的线程改了" 但是按照msdn上面对Control.InvokeRequired的说明,这是不应该发生的;

        public void SetProcessInfo(int value)
        {
            //System.Diagnostics.Debug.WriteLine("if:" + progressBar1.InvokeRequired);
            if (progressBar1.InvokeRequired)
            {
                System.Diagnostics.Debug.WriteLine("if true:" + progressBar1.InvokeRequired);

                Action<int> setProcessDelegate = new Action<int>(SetProcessInfo);
                progressBar1.Invoke(setProcessDelegate, value);
            }
            else
            {
                //System.Diagnostics.Debug.WriteLine("else start:" + progressBar1.InvokeRequired);
                if (value > progressBar1.Maximum)
                    value = progressBar1.Maximum;
                if (value < progressBar1.Minimum)
                    value = progressBar1.Minimum;

                //System.Diagnostics.Debug.WriteLine("else end:" + progressBar1.InvokeRequired);
                progressBar1.Value = value;
            }
        }
这时候我输出显示: 程序启动 就直接击中了断点2;说明根本就没执行过if里面的内容
phommy 2013-01-25
  • 打赏
  • 举报
回复
还真没见过,你试试右键工具条把debug location调出来,f11单步看 有可能你f11一下跟踪就换别的线程了,经常会 另外watch中的表达式有时会有副作用,你看下有没有可疑的 然后是检查设置中debug下“暂停时暂停所有线程”是不是打勾了
加载更多回复(5)

110,499

社区成员

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

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

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