【问题标题】:What is causing my UI Thread to be blocked?是什么导致我的 UI 线程被阻止?
【发布时间】:2015-12-08 07:02:36
【问题描述】:

我完全迷失了真正导致问题的原因。因此,与其试图解释问题,不如直接进入有问题的代码。这是我的程序的布局:

    private void connection_OnMessage(object sender, agsXMPP.protocol.client.Message msg)
    {     
        if (!string.IsNullOrEmpty(msg.Body) && ((msg.XDelay != null && msg.XDelay.Stamp.Date == DateTime.Today) || msg.XDelay == null))
        {
            agsXMPP.Jid JID = new Jid(msg.From.Bare);

            int rowIndex = chatLog.Rows.Add();
            chatLog.Rows[rowIndex].Cells["chatNameColumn"].Value = JID.User;
            chatLog.Rows[rowIndex].Cells["chatMessageColumn"].Value = msg.Body;

            //Begin line of the problem
            if (IncomingMessage != null)
                IncomingMessage(this, JID.User, msg.Body);
            //End of the problem
        }
    }

上面的代码sn-p是A类的,程序启动后,这个类就连接到服务器了。连接后,此代码 sn-p 会快速触发大约 20 次,每行消息一次。 (聊天日志中已经有大约 20 行消息。)由于只有一条消息通过 if 条件,因此注释问题的行只运行一次。这些行会触发 B 类下面的代码 sn-p。

(大约在 A 类触发的时候,我有另一个像 A 的类会触发类似的事件,由 B 类以相同的方式处理,这将由 C 类处理。)

    private void newSource_IncomingMessage(IChatSource sender, string user, string message)
    {
        UpdatedMessageEventHandler temp = UpdatedMessage;
        if (temp != null)
            temp(sender, user, message);
    }

上面B类的代码sn-p触发下面C类的代码sn-p。

    private void chatManager_UpdatedMessage(IChatSource source, string user, string message)
    {
        if (!source.Muted)
        {
            updateMessage(source, user, message);
        }            
    }

    delegate void UpdateMessageCallback(IChatSource source, string user, string message);

    private void updateMessage(IChatSource source, string user, string message)
    {
        if (allDataGridView.InvokeRequired)
        {
            UpdateMessageCallback d = new UpdateMessageCallback(updateMessage);
            Invoke(d, new object[] { source, user, message });
        }
        else
        {
            int row = allDataGridView.Rows.Add();
            allDataGridView.Rows[row].DefaultCellStyle.ForeColor = source.TextColor;
            allDataGridView.Rows[row].Cells["NameColumn"].Value = user;
            allDataGridView.Rows[row].Cells["MessageColumn"].Value = message;

            if (!MenuItem.Checked)
            {
                MenuItem.Checked = true;
                Show();
            }
        }
    }

这是我试图解决问题的方法,但代码已被删除:

  1. 我尝试为某些代码加锁。
  2. 我尝试将某些代码放在单独的线程上并让它们运行。

这是发生了什么:

  1. 当我运行程序时,UI 线程似乎被阻塞了。换句话说,C 类不会被涂漆。有时,表单甚至都不会出现。
  2. 有几次,它抱怨一个奇怪的错误“调用方法时发生错误。目标线程不再存在。”
  3. 如果我注释掉问题行,一切正常,但这是奇怪的部分。如果我在 A 类中创建一个计时器对象并让它以相同的方式触发事件,它就可以正常工作。
  4. 在调试模式下单步执行时,有时我可以正常工作,但大多数时候都失败了。
  5. 有几次,我遇到 InvalidOperationException 并显示以下消息:“控件从创建它的线程以外的线程访问”。即使我确实使它成为线程安全的。

总之,我不知道是什么导致了 UI 线程被阻塞。任何指针或我可能做错了什么?

【问题讨论】:

  • 看来connection_OnMessage 是从另一个线程调用的。 你能显示连接的初始化吗? connection.connect 是否阻塞了 gui 线程?所以调用会导致挂起。
  • 当您使用调试器和 UI 线程块运行此程序时,您可以暂停进程并查看当时正在运行的线程以及它们在做什么。你能说明会发生什么吗? UI 线程应该等待某个调用返回,知道是哪个调用可能会有所帮助。
  • updateMessage 中检查allDataGridView.InvokeRequired,但在this 上调用Invoke 而不是allDataGridView。这很可能无关紧要,只是为了确保您的所有控件都在一个线程上。顺便说一句:也许BeginInvoke 而不是Invoke 也有帮助,但首先我们应该找出它为什么会挂起。
  • "4. .... 但大多数情况下,它会失败" 究竟是什么意思?
  • @JeroenvanLangen,connection_OnMessage 是一个事件处理程序,当有来自连接的消息时触发。如下初始化:connection.Open(); connection.OnLogin += new ObjectHandler(connection_OnLogin); connection.OnError += new ErrorHandler(connection_OnError); connection.OnMessage += new agsXMPP.protocol.client.MessageHandler(connection_OnMessage);

标签: c# multithreading winforms event-handling thread-safety


【解决方案1】:

问题是您正在调用跨线程方法。这可能导致死锁。

您可以解决这个问题,将消息添加到并发队列和计时器(gui 线程)上,检查队列并将消息添加到控件。

这不是一个完整的解决方案,而是一种防止跨线程方法调用的方法

喜欢:(PSEUDO)(在网站上在线写)

// dataholder
public class ChatMsg
{
    public string User {get;set;}
    public string Message {get;set;}
}

// message store
private List<ChatMsg> _messages = new List<ChatMsg>();
// timer
private Timer _timer;

// callback for you chatapi?? (like you wrote)
private void newSource_IncomingMessage(IChatSource sender, string user, string message)
{
    UpdatedMessageEventHandler temp = UpdatedMessage;

    // lock the store
    lock(_messages)
        _messages.Add(new ChatMsg { User = user, Message = message });
}

// constructor
public Form1()
{
    // create the check timer.
    _timer = new Timer();
    _timer.Interval = 100;
    _timer.Tick += Timer_Tick;
    _timer.Start();
}

// timer method
private void Timer_Tick(object sender, EventArgs e)
{
    // copy of the queue
    ChatMsg[] msgs;

    // lock the store and 'move' the messages
    lock(_messages)
    {
        msgs = _messages.ToArray();
        _messages.Clear();
    }

    if(msgs.Length == 0)
        return;

    // add them to the controls
    foreach(var msg in msgs)
    {
        // add the message to the gui controls... (copied from your question)
        int row = allDataGridView.Rows.Add();
        allDataGridView.Rows[row].DefaultCellStyle.ForeColor = source.TextColor;
        allDataGridView.Rows[row].Cells["NameColumn"].Value = user;
        allDataGridView.Rows[row].Cells["MessageColumn"].Value = message;
   }
}

类似的东西..

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-07
    • 1970-01-01
    • 2016-01-11
    • 2016-09-24
    • 2011-12-19
    • 1970-01-01
    相关资源
    最近更新 更多