飘易博客(作者:Flymorn)
订阅《飘易博客》RSS,第一时间查看最新文章!
飘易首页 | 留言本 | 关于我 | 订阅Feed

c#多线程频繁操作同一控件“未将对象引用设置到对象的实例”

Author:飘易 Source:飘易
Categories:C#编程 PostTime:2012-11-24 19:55:52
正 文:
    c#软件设计中,如果有多个线程同时地频繁地操作UI主线程同一个控件时,比如显示状态等,往往会出现“未将对象引用设置到对象的实例”错误。

    有时即使你使用try...catch...来捕获错误依然无济于事,比如飘易最近的一个小开发应用。5个线程需要频繁的把状态写入到 RichTextBox 这个控件上,5个线程同时不停地写入数据,开始还好,但是运行一段时间后,就会出现“未将对象引用设置到对象的实例”错误,或者软件直接自动退出了。

    这样的异常客户一定不满意。我猜测问题还是出现在多线程身上,即使加上了以下这句:
Control.CheckForIllegalCrossThreadCalls = false; 
    以便允许线程的不安全调用,.net内部还是会出现错误。

    解决方法就是使用字符串 StringBuilder类(string也可以,但是效率不如StringBuilder),临时缓存要显示的数据,然后用一个定时器,定时写入到RichTextBox这个控件上,这样就避免了频繁的操作同一个控件

StringBuilder tmpv = new StringBuilder();
tmpv.AppendLine("...");

    定时器间隔可以设为5秒,每隔5秒把tmpv里的数据显示到RichTextBox即可。

private void timer1_Tick(object sender, EventArgs e)
        {//定时器
            try 
            {
                // 允许子线程更新UI线程里的控件
                this.Invoke((EventHandler)delegate
                {
                    richTextBox1.AppendText(tmpv.ToString());
                    tmpv.Length = 0; //清空StringBuilder 
                });
            }
            catch (Exception ){}
        }

    如有疑问,欢迎您留言。


    List<T>非线程安全

    在多线程里,尤其要注意 List<T> 的使用,List不是线程安全的。List的Add以及List.ToArray等方法是线程不安全的!

    List的源码中的Add方法,使用了每次当当前的元素达到上限,通过创建一个新的数组实例,并给长度翻倍的操作。如果单线程操作不会有问题,直接扩容,然后继续往里面加值。当多个线程同时添加元素,且刚好它们都执行到了扩容这个阶段,当一个线程扩大了这个数组的长度,且进行了+1操作后,另外一个线程刚好也在执行扩容的操作,这个时候它给Capacity的值设为2048,但是另外一个线程已经将this._size设为2049了,所以这个时候就报异常了.当然不止这一个问题,还有Copy的时候也会出问题,如果里面的元素过多,另外一个线程拿到空值的几率很大。
c#多线程频繁操作同一控件“未将对象引用设置到对象的实例”

    如果不注意处理,就会出现错误:
在 System.Windows.Forms.RichTextBox.EditStreamProc(IntPtr dwCookie, IntPtr buf, Int32 cb, Int32& transferred)
在 System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
在 System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
在 System.Windows.Forms.Control.WndProc(Message& m)
在 System.Windows.Forms.RichTextBox.WndProc(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
在 System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

    怎么解决呢?解决方案:

1.扩容List的初始容量为集合需要的实际容量或更大;
2.给List.Add等方法加锁;
3.使用List的线程安全版本。

    下来飘易来介绍下加锁 lock 的方案:
private static readonly Object Locker = new Object();
List<string> logList = new List<string>();

lock (Locker)
{
    string cs = string.Join("\r\n", logList.ToArray());
    logList.Add("...");
}

【参考】:
C# 多线程之List的线程安全问题
作者:飘易
来源:飘易
版权所有。转载时必须以链接形式注明作者和原始出处及本声明。
上一篇:解决Windows Server 2003 补丁(KB2686509)安装不了
下一篇:C#向RichTextBox写入数据并始终显示在最下方
2条评论 “c#多线程频繁操作同一控件“未将对象引用设置到对象的实例””
1 wllaoshu@qq.com
2012-11-25 17:40:45
线程 只是负责写到StringBuilder 里面吗?
2012-12-22 11:36:10
负责至少写到StringBuilder 里面吗
发表评论
名称(*必填)
邮件(选填)
网站(选填)

记住我,下次回复时不用重新输入个人信息
© 2007-2019 飘易博客 Www.Piaoyi.Org 原创文章版权由飘易所有