【问题标题】:WinForms UI sometimes non-responsiveWinForms UI 有时无响应
【发布时间】:2013-08-04 04:30:18
【问题描述】:

我正在更新与 GSM 调制解调器对话的 WinForms 应用程序,简而言之,执行以下功能:

  • 接受传入的 SMS 消息并将其上传到基于 SOAP 的 Web 服务
  • 从基于 SOAP 的 Web 服务下载消息,将它们转换为 SMS 消息并将它们发送到目标地址

这个功能是这样划分的:

  1. 处理传入 SMS 消息的代码是 GSM 库的一部分。它被设置为当调制解调器接收到新的传入消息时触发的事件处理程序。事件处理程序只是将消息转换为特定的字符串格式(源电话号码、消息正文)并将其添加到队列以上传到 Web 服务。

  2. 将消息上传到 Web 服务的代码作为一个任务运行,该任务由一个间隔为 15 秒的计时器启动。此代码只是将所有 SMS 消息出列,将它们上传到 Web 服务,更新 UI 中的标签以指示队列中没有消息,然后终止。

  3. 从 Web 服务下载消息的代码也作为任务运行,它也由间隔为 15 秒的计时器触发。它只是从 Web 服务下载任何新消息,将它们添加到固定大小的“传出”队列,更新 UI 中的标签以指示队列中的消息数量,然后终止。

  4. 最后,构造和发送队列中传出 SMS 消息的代码作为任务运行。这段代码在一个相当紧凑的循环中运行,当传出队列中有消息时,它会不断地发送消息。此代码还会在从队列中删除消息并将其发送到目标电话号码后更新 UI 中的标签。

重述一下,上面的第 2、3 和 4 项作为任务运行。我将相同的取消令牌传递给每个任务,因此如果用户关闭我的应用程序,我可以取消它们。除了以下情况外,一切似乎都按预期工作:如果 GSM 调制解调器正在运行但我的应用程序没有运行,则传入的 SMS 消息将存储在 SIM 卡上,直到它已满(在这种情况下为 30 条消息),然后,他们将开始在 SMSC 排队。当我的应用程序启动时,我会检查存储在 SIM 卡上的消息并进行处理。如果 SIM 卡已满,只要我开始处理和删除这些消息,在 SMSC 排队的任何消息都将开始传送。正是在这一点上,有时我的 UI 会冻结。我基本上有三段代码来更新 UI,它们被我上面描述的各种任务调用:

    // update the status textbox (a multi-line textbox)
    private void UpdateStatusText(string text)
    {
        textBoxStatus.BeginInvoke((MethodInvoker)delegate { textBoxStatus.AppendText(text + Environment.NewLine); });
    }

    // update UI with the current number of messages in the incoming queue
    private void UpdateIncomingMessageQueueCount()
    {
        labelIncomingQueueCounter.BeginInvoke((MethodInvoker)delegate { labelIncomingQueueCounter.Text = messagesFromDevicesQueue.GetCount().ToString(); });
    }

    // update the UI with the current number of messages in the outgoing queue
    private void UpdateOutgoingMessageQueueCount()
    {
        labelOutgoingQueueCounter.BeginInvoke((MethodInvoker)delegate { labelOutgoingQueueCounter.Text = messagesToDevicesQueue.GetCount().ToString(); });
    }

我不完全确定导致冻结发生的原因,但是当它发生并且我能够在调试器中中断时,我可以看到一些状态为“已阻止”的任务,并且悬停在提示上显示“没有可用的等待信息”。正如我之前所说,这似乎只在有大量传入消息时才会发生。在这种情况下,我调用 UpdateIncomingMessageQueueCount() 相当数量(在每个传入消息入队之后)。在 UpdateIncomingMessageQueueCount() 的情况下,它通过 queues .GetCount() 方法获取队列中的消息数。固定大小的队列对象是这样实现的:

public class FixedSizeQueue<T>
{

private readonly List<T> queue = new List<T>();
private readonly object syncObj = new object();

public int Size { get; private set; }

public FixedSizeQueue(int size)
{
    Size = size;
}

public void Enqueue(T obj)
{
    lock (syncObj)
    {
        queue.Insert(0, obj);

        if (queue.Count > Size)
        {
            queue.RemoveRange(Size, queue.Count - Size);
        }
    }
}

public T[] DequeueAllItems()
{
    lock (syncObj)
    {
        var result = queue.ToArray();
        queue.Clear();
        return result;
    }
}

public void RemoveFirstItem()
{
    lock (syncObj)
    {
        if (queue.Count > 0)
        {
            queue.RemoveAt(0);
        }
    }
}

public T Peek()
{
    lock (syncObj)
    {
        if (queue.Count > 0)
        {
            var result = queue[0];
            return result;
        }
        else
        {
            return default(T);
        }
    }
}

public void Flush()
{
    lock (syncObj)
    {
        queue.Clear();
    }
}

public int GetCount()
{
    lock (syncObj)
    {
        return queue.Count;
    }
}
}

由于我使用 .BeginInvoke() 来更新 UI,我想知道我的性能问题是否与对队列对象调用 .GetCount() 的大量异步尝试有关? UI 最终会再次响应,但这显然不应该发生。

【问题讨论】:

    标签: c# multithreading winforms task-parallel-library


    【解决方案1】:

    您正在为每条传入的消息调用 BeginInvoke。这最终将一大堆 UI 更新排入队列,这些更新一个接一个地发生,从而有效地阻塞了 UI 线程。

    您最好为每条消息致电Invoke。或者,甚至更好的是,更改您的设计,以便您清空队列并一次性使用所有消息进行一次 UI 更新。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-09
      • 2011-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多