如何在C#中传输数据包时正确保持UI更新?

时间:2012-04-08 21:54:29

标签: c# multithreading sockets packet-capture ui-thread

我有这种形式产生一个新线程并开始监听并等待循环中的UDP数据包。我需要的是使用接收的字节数来更新UI。

为此,我设置了一个事件,一旦收到数据包就会提出这个事件,并将接收到的字节数作为参数传递。由于我没有在UI线程上运行,我不能简单地直接更新UI。这就是我目前正在做的事情:

private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) {
    if(InvokeRequired) {
        Invoke(new MethodInvoker(() => {
            totalReceivedBytes += receivedBytes;
            Label.Text = totalReceivedBytes.ToString("##,0");
        }));
    }
}

但是这仍然在与数据包接收循环相同的线程上运行并且它不会返回到该循环 - 并等待另一个数据包 - 直到此EVENTHANDLER_UpdateTransferProgress方法返回。 / p>

我的问题基本上是关于上述方法中的以下几行:

Label.Text = totalReceivedBytes.ToString("##,0");

像这样更新UI会减慢数据包接收速度。如果我取消该行(或评论它),数据包接收将会快得多。

我怎么可能解决这个问题?我认为更多线程是关键,但我不确定如何在这种情况下正确实现它们......我正在使用带有.NET 2.0的Windows Forms。

修改

在我之前的测试中,上述情况似乎是正确的,实际上可能在某种程度上。但经过一些测试后,我意识到问题出在整个Invoke(new MethodInvoker(() => { ... }));上。当我删除它(当然不会更新UI)并离开EVENTHANDLER_UpdateTransferProgress但继续提升事件时,数据包接收速度会快得多。

我测试了接收一些平均大约需要1.5秒的文件而根本没有在事件处理程序上调用Invoke()。当我在事件处理程序中调用Invoke()时,即使没有更新UI中的任何控件或进行任何操作(换句话说,匿名方法体是空的),它花费的时间更长,约为5.5秒。你可以看到它有很大的不同。

有没有改善这个?

3 个答案:

答案 0 :(得分:3)

您的方法存在的问题是它会更新每个数据包的UI。如果您每秒收到1000个数据包,则每秒更新UI 1000次!显示器每秒刷新的次数可能不会超过100次,如果每秒更新次数超过10次,则无法读取它。

解决此问题的一个更好的方法是将totalReceivedBytes += receivedBytes;放在处理I / O的线程中,并将一个计时器放在每秒只执行Label.Text = totalReceivedBytes.ToString("##,0");一次的UI线程上最。传输开始时,启动计时器;当转移停止时,停止计时器。

答案 1 :(得分:1)

是的,有办法改善这一点。

第一种是使用BeginInvoke而不是Invoke,它不会等待调用返回。您还应该考虑在方法中使用其他表单

private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) {
    if(InvokeRequired) {
        BeginInvoke(new Action<long>(EVENTHANDLER_UpdateTransferProgress),
                    receivedBytes));
        return;
    }
    totalReceivedBytes += receivedBytes;
    Label.Text = totalReceivedBytes.ToString("##,0");
}

因此,如果从不需要调用的方法调用此方法,则仍会执行GUI上的更新。


您可以做的另一个选择是破坏下载线程中的线程。

之类的东西
public event EventHandler<MonitorEventArgs> ReportProgress;

public void startSendingUpdates(MonitorEventArgs args) {
  EventHandler<MonitorEventArgs> handler = ReportProgress;
  if (handler == null) {
      return;
  }
  ThreadPool.QueueUserWorkItem(delegate {
      while (!args.Complete) {
          handler(this, args);
          Thread.Sleep(800);
      }
  });
}

public void download() {
    MonitorEventArgs args = new MonitorEventArgs();
    startSendingUpdates(args);
    while (downloading) {
        int read = downloadData(bytes);
        args.BytesTransferred += read;
    }
    args.Complete = true;
}

public class MonitorEventArgs : EventArgs {
    public bool Complete { get; set; }
    public long BytesTransferred { get; set; }
}

与优点相比,这种开销很小。您的下载线程不受GUI更新的影响(至少与等待GUI更新相比)。缺点是你在线程池中占据一个线程,但是,嘿,这就是他们的目的!并且,线程在完成后关闭,因为您设置了完整标志。设置时也不需要锁定,因为工作线程中的额外运行在上下文中不重要。

答案 2 :(得分:0)

您是否尝试过使用BeginInvoke而不是Invoke? BeginInvoke()是异步调用。

private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) {
    if(InvokeRequired) {
        BeginInvoke(new MethodInvoker(() => {
            totalReceivedBytes += receivedBytes;
            Label.Text = totalReceivedBytes.ToString("##,0");
        }));
    }
}