temz 2023-04-18 11:03 采纳率: 100%
浏览 65
已结题

C#委托和多线程问题

有form类Dbview,form类form1,线程类C。Dbview是主类,form1从Dbview启动(点击监控按钮),Dbview界面点击连接按钮后根据输入数据多少启动1到n个线程(线程方法实现根据线程类C)称这些线程为C1至Cn线程,C1到Cn使用线程类C的方法从opc循环获取数据,再通过2个委托传值给Dbview和form1。form1中用datagridview显示数据,使用方法getopcdata(getopcdata方法写在form1中)和线程类C中委托绑定,有数据就传送到getopcdata,再在方法体中修改datagridview的值。
现在一个问题是,我是用非创建datagirdview的线程修改datagirdview中表格的值(不使用invoke直接修改UI),并没有每次都弹出异常,而是极少次数报异常,这是为什么。
程序的使用是先点击连接按钮启动线程获取opc数据,然后点击监控按钮启动form1让获取的数据显示在form1上。
第二个问题是在使用invoke时,先点击连接按钮再点击监控可以正常显示,但是一旦关了form1,c1到cn线程就不再运行了,必须点连接重新启动c1-cn线程,但是不使用invoke直接修改UI时c1到cn就可以一直正常运行,这是为什么,如何在关闭form1之后继续使c1-cn正常运行
下面是在getopcdata中修改ui的代码(不使用invoke直接修改UI)

        public void getopcdata(Dictionary<string, Store> dictbuffer)
        {
            try
            {
                if (dictoutput.Count > 0)
                {
                    dataGridView1.RowCount = dictoutput.Count;
                    for (int i = 0; i < dictoutput.Count; i++)
                    {
                        foreach (var item in dictbuffer)
                        {
                            if (item.Key.ToString().Equals(dataGridView1.Rows[i].Cells[0].Value.ToString()))
                            {
                                dataGridView1.Rows[i].Cells[1].Value = item.Value.value;

                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }

当我使用跨线程Invoke的时候getopcdata方法如下

     public void getopcdata(Dictionary<string, Store> dictbuffer)
        {
            if (dataGridView1.InvokeRequired)
            {
                dataGridView1.Invoke(new MethodInvoker(delegate ()
                {
                    if (dictoutput.Count > 0)
                    {
                        dataGridView1.RowCount = dictoutput.Count;
                        for (int i = 0; i < dictoutput.Count; i++)
                        {
                            foreach (var item in dictbuffer)
                            {
                                if (item.Key.ToString().Equals(dataGridView1.Rows[i].Cells[0].Value.ToString()))
                                {
                                    dataGridView1.Rows[i].Cells[1].Value = item.Value.value;
                                }
                            }
                        }
                    }
                }));
            }
     }

线程类C代码

    public class OpcRead
    {
        public delegate void MyCallback(Dictionary<string, Store> dictbuffer);
        private MyCallback myCallback;
        private MyCallback form1Callback;

        public Dictionary<string, string> dict = new Dictionary<string, string>();
        public OPCUAHelper opc;
        public string root_url;

        public OpcRead(Dictionary<string, string> dict1, OPCUAHelper opc, string root_url, MyCallback callback,MyCallback callbackform1)
        {
            this.myCallback = callback;
            this.form1Callback = callbackform1;
            this.dict = dict1;
            this.opc = opc;
            this.root_url = root_url;
        }

        public void readopc()
        { 
           Dictionary<string, Store> dictbuffer=new Dictionary<string, Store>();

            while (GlassProcess.DbView.running)
            {
                if (dict.Count>0) 
                {
                    for (int i = 0; i < dict.Count; i++)
                    {
                        var name = dict.Keys.ElementAt(i);
                        var connection = dict.Values.ElementAt(i);
                        string threadname = Thread.CurrentThread.Name;
                        if (!connection.Equals("null"))
                        {
                            var value = opc.GetCurrentNodeValue<ValueType>(connection);
                            if (value == null)
                            { 
                                MessageBox.Show(name + "读取错误");
                                Store store = new Store(name,connection,threadname);
                                GlassProcess.DbView.dictretry.Add(name,store);
                                dict.Remove(name);
                                break;
                            }
                            else
                            {
                                Store store = new Store(name,root_url,threadname,value);
                                if (!dictbuffer.ContainsKey(name)) 
                                {
                                    dictbuffer.Add(name, store);
                                }
                                else if (dictbuffer.ContainsKey(name))
                                {
                                    dictbuffer.Remove(name);
                                    dictbuffer.Add(name, store);
                                }
                                
                            }
                        }
                    }
                    myCallback(dictbuffer);           //通过委托将dictbuffer发送给Dbview
                    form1Callback(dictbuffer);       //通过委托将dictbuffer发送给form1
                    dictbuffer.Clear();
                }
                Thread.Sleep(100);
            }
        }
    }
}

  • 写回答

6条回答 默认 最新

  • 极客智能体-在线 2023-04-20 02:53
    关注

    以下答案由GPT-3.5大模型与博主波罗歌共同编写:
    关于第一个问题:为什么直接修改UI不会每次报异常?

    首先,需要明确的是,UI控件只能由创建它们的线程直接访问和操作,其他线程必须通过控件的Invoke或BeginInvoke方法将操作委托给创建它们的线程。如果你直接在非UI线程修改UI控件,那么它不是安全的,因为不同的线程可能会在不同的时间修改相同的控件,这可能会导致控件状态的不确定性。

    但是,在某些情况下,直接修改UI控件可能会工作。这可能是由于线程调度和控件状态之间的幸运组合。然而,这是一种不稳定的方法,不应该用于生产代码。应该始终遵守跨线程操作UI控件的最佳实践,即使用Invoke或BeginInvoke方法。

    关于第二个问题:使用Invoke同时关闭form1后为什么C1到Cn线程不再运行?

    这个问题是由于在使用Invoke调用UI线程操作时,如果UI线程已经结束(例如关闭了form1),那么调用可能会失败。因此,在使用Invoke方法时,应该检查是否可以访问UI控件,如果不能,则忽略控件更新并且不应该使任务失败。这可能是您的线程类没有这个逻辑,所以Invoke方法失败后线程对象退出。

    因此,为了让线程类能够在关闭form1后继续运行,您需要修改getopcdata和OpcRead方法,使其能够在UI线程不存在时处理UI控件操作。以下是相应的修改代码:

    getopcdata方法:

    public void getopcdata(Dictionary<string, Store> dictbuffer)
    {
        if (dataGridView1.InvokeRequired)
        {
            dataGridView1.Invoke(new MethodInvoker(delegate ()
            {
                if (!IsDisposed && !Disposing)
                {
                    if (dictoutput.Count > 0)
                    {
                        dataGridView1.RowCount = dictoutput.Count;
                        for (int i = 0; i < dictoutput.Count; i++)
                        {
                            foreach (var item in dictbuffer)
                            {
                                if (item.Key.ToString().Equals(dataGridView1.Rows[i].Cells[0].Value.ToString()))
                                {
                                    dataGridView1.Rows[i].Cells[1].Value = item.Value.value;
                                }
                            }
                        }
                    }
                }
            }));
        }
    }
    

    在方法中添加了检查UI线程对象是否正在被Dispose的逻辑,如果是,则忽略控件更新。

    OpcRead方法:

    public void readopc()
    { 
        Dictionary<string, Store> dictbuffer=new Dictionary<string, Store>();
    
        while (GlassProcess.DbView.running)
        {
            if (dict.Count>0) 
            {
                for (int i = 0; i < dict.Count; i++)
                {
                    var name = dict.Keys.ElementAt(i);
                    var connection = dict.Values.ElementAt(i);
                    string threadname = Thread.CurrentThread.Name;
                    if (!connection.Equals("null"))
                    {
                        var value = opc.GetCurrentNodeValue<ValueType>(connection);
                        if (value == null)
                        {
                            MessageBox.Show(name + "读取错误");
                            Store store = new Store(name,connection,threadname);
                            GlassProcess.DbView.dictretry.Add(name,store);
                            dict.Remove(name);
                            break;
                        }
                        else
                        {
                            Store store = new Store(name,root_url,threadname,value);
                            if (!dictbuffer.ContainsKey(name)) 
                            {
                                dictbuffer.Add(name, store);
                            }
                            else if (dictbuffer.ContainsKey(name))
                            {
                                dictbuffer.Remove(name);
                                dictbuffer.Add(name, store);
                            }
                                    
                        }
                    }
                }
                if (!IsDisposed && !Disposing)
                {
                    form1Callback(dictbuffer);       //通过委托将dictbuffer发送给form1
                }
                myCallback(dictbuffer);           //通过委托将dictbuffer发送给Dbview
                dictbuffer.Clear();
            }
            Thread.Sleep(100);
        }
    }
    

    在方法中添加了检查UI线程对象是否正在被Dispose的逻辑,如果是,则不需要调用form1Callback。

    另外,您还需要在form1的Dispose方法中添加以下代码,在关闭窗口时清除对委托的引用,防止引起内存泄露:

    protected override void Dispose(bool disposing)
    {
        if (disposing && components != null)
        {
            // 清除委托引用
            dbviewCallback = null;
        }
        base.Dispose(disposing);
    }
    

    如果我的回答解决了您的问题,请采纳!

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(5条)

报告相同问题?

问题事件

  • 系统已结题 4月28日
  • 已采纳回答 4月20日
  • 创建了问题 4月18日

悬赏问题

  • ¥15 AT89C51控制8位八段数码管显示时钟。
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 下图接收小电路,谁知道原理
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本
  • ¥15 手机接入宽带网线,如何释放宽带全部速度
  • ¥30 关于#r语言#的问题:如何对R语言中mfgarch包中构建的garch-midas模型进行样本内长期波动率预测和样本外长期波动率预测
  • ¥15 ETLCloud 处理json多层级问题
  • ¥15 matlab中使用gurobi时报错
  • ¥15 这个主板怎么能扩出一两个sata口