是什么导致我的UI线程被阻止?

时间:2015-12-08 07:02:36

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

我完全迷失了导致问题的真正原因。因此,我试图解释这个问题,不妨直接找到问题所在的代码。这是我的程序的布局:

    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
        }
    }

上面的代码片段是A类。启动程序后,此类建立与服务器的连接。在连接之后,这段代码片段被快速触发约20次,每行消息一次。 (聊天记录中已经有大约20行消息。)由于只有一条消息通过if条件,因此对该问题进行评论的行只运行一次。这些行会触发B类下面的代码片段。

(在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类代码片段会触发C类下面的代码片段。

    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. 我试图将某些代码放在一个单独的线程上并让它们运行。
  3. 以下是发生的事情:

    1. 当我运行程序时,UI线程似乎被阻止了。换句话说,C级没有画画。有时,表格甚至不会出现。
    2. 有几次,它抱怨一个奇怪的错误“调用该方法时出错。目标线程不再存在。”
    3. 如果我评论问题行,一切正常,但这是奇怪的部分。如果我在A类中创建一个计时器对象并让它以相同的方式触发事件,它就可以正常工作。
    4. 在调试模式下进行行步调试时,我有时可以正常工作,但大部分时间都会失败。
    5. 有几次,我遇到了InvalidOperationException,其中包含“从其创建的线程以外的线程访问的控件”。即使我确实让它线程安全。
    6. 总之,我不知道导致UI线程被阻止的原因。任何指针或我可能做错了什么?

1 个答案:

答案 0 :(得分:0)

问题是你正在调用方法交叉线程。这可能导致死锁。

您可以解决此问题,在并发队列和计时器(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;
   }
}

像这样......