关于#c##的问题:winform中利用delegate更新ui的顺序错误

在做一个简单地winform工控机,主要使用TCP连接。但不知道为说明在调用delegate时会产生错序的情况。

我在主界面上面设置了一个Status Strip来作为状态提示。然后在全局变量中声明委托,这样我就可以在异步创建Tcp连接时去更新主界面的状态提示。

namespace RsmiMonitorForWifi
{
    ///全局delegate
    public delegate void StatusUpdate(string infor);

    internal static class Program
    {
        /// 
        /// 应用程序的主入口点。
        /// 
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new frmModelSelect());
        }
    }
}
frmMain.cs(1/2)
public static StatusUpdate statusUpdate = null;

public frmMain()
        {
            InitializeComponent();
  
            statusUpdate = new StatusUpdate(StatusUpdate);
            tcpMethod = new tcpMethod("192.168.1.39",8080);
            TcpClient tcpClient = tcpMethod.TcpCreateAsync();
        }

TcpMethod.cs
public TcpClient TcpCreateAsync()
        {
            tcpClient = new TcpClient();
            try
            {
                tcpClient.BeginConnect(iP, iPort, new AsyncCallback(ConnectedAsync), tcpClient);
                _tcpBusy = true;
                frmMain.statusUpdate("正在尝试连接网络...");
            }
            catch (Exception e)
            {
                frmMain.statusUpdate(e.Message);
            }
            return tcpClient;
        }

        //连接回调
        private void ConnectedAsync(IAsyncResult asyncResult)
        {
            tcpClient = asyncResult.AsyncState as TcpClient;
            if (tcpClient != null)
            {
                try
                {
                    tcpClient.EndConnect(asyncResult);
                    frmMain.statusUpdate("连接成功");
                }
                catch (SocketException e)
                {
                    frmMain.statusUpdate(e.Message);
                }
            }
            _tcpBusy = false;
        }

问题出在下面这一段,因为发现单个statuslabel的显示效果不好,我想要让statuslabel1储存statuslabel2的内容,用stauslabel2去显示最新的状态信息,如图:

img

frmMain.cs(2/2)
private void StatusUpdate(string infor)
        {
            string currentTime = DateTime.Now.ToString("HH:mm:ss");
            //不知道为什么delegate的调用顺序会出问题,暂时只使用一个text
            //statuslb_currentstate1.Text = statuslb_currentstate2.Text;
            statuslb_currentstate2.Text = $"{currentTime} {infor}";
        }

测试下来在第一次连接时表现良好,但当我第二次创建连接时,理论上应该更新两次时实际只更新了一次,表现正常的情况下label1的text应为11:08分的 "正在尝试连接", label2的text应为11:08分后的 "连接成功"

img

Debug后发现主页面更新时,先更新了"连接成功"

img


然后label1的text没有被更新,紧接着label2的值被更新成 "正在尝试连接",造成了看上去只有label2被更新了一次的假象。

之后我发现debug时情况会有所不同,有时可以又是不行。我的猜测是我对线程运作的理解有误,导致主线程更新控件时的执行顺序随机。怎么样才能稳定的实现我想要的效果呢

在主线程里
tcpClient.BeginConnect(iP, iPort, new AsyncCallback(ConnectedAsync), tcpClient);
_tcpBusy = true;
frmMain.statusUpdate("正在尝试连接网络...");

BeginConnect里使用了new AsyncCallback(ConnectedAsync),系统就会另起一个线程执行回调函数,这里就有 主线程和 执行回调ConnectedAsync的单独线程,如果当主线程刚刚把 _tcpBusy = true;执行完,还没来得及执行 frmMain.statusUpdate("正在尝试连接网络...");,就暂停了,这时系统执行 回调ConnectedAsync的线程,自然 先执行 frmMain.statusUpdate("连接成功"),这时状态更新就错误了,
所以 _tcpBusy = true;
frmMain.statusUpdate("正在尝试连接网络..."); 这两句应该加载 回调函数 tcpClient.EndConnect(asyncResult);的前面

_tcpBusy = true;
frmMain.statusUpdate("正在尝试连接网络...");
tcpClient.EndConnect(asyncResult);
frmMain.statusUpdate("连接成功");

我们在异步调用委托的时,线程的执行顺序是不知道的,可能产生错误结果,让委托的顺序出现错误。

可以用Control.Invoke()方法将委托调用抛到主线程上,解决线程的问题。

修改frmMain.cs(2/2)试试:

private void StatusUpdate(string infor)
{
if (statuslb_currentstate2.InvokeRequired)
{
statuslb_currentstate2.Invoke(new StatusUpdate(StatusUpdate), new object[] { infor });
}
else
{
string currentTime = DateTime.Now.ToString("HH:mm:ss");
statuslb_currentstate2.Text = $"{currentTime} {infor}";
}
}


你的代码都没有异步的,只是名字加了Async?
可以用System.Threading的Progress来更新状态

不知道你这个问题是否已经解决, 如果还没有解决的话:

如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^