2

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

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

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

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

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

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

像这样更新 UI 会减慢数据包的接收速度。如果我把那条线去掉(或评论它),数据包接收会快得多。

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

编辑:

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

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

有没有办法改善这一点?

4

3 回答 3

3

您的方法的问题在于它会更新每个数据包的 UI。如果您每秒收到 1000 个数据包,您将每秒更新 UI 1000 次!监视器可能每秒刷新不超过 100 次,如果每秒更新超过 10 次,则没有人能够读取它。

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

于 2012-04-09T01:48:17.640 回答
1

是的,有一种方法可以改善这一点。

第一种是使用BeginInvokeInvoke不是等待调用返回。您还应该考虑在您的方法中使用另一种形式

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 更新相比没有)。缺点是你在线程池中占用了一个线程,但是,嘿,这就是他们的目的!而且,线程完成后会关闭,因为您设置了完成标志。设置时也不需要锁定,因为工作线程中的额外运行在上下文中并不重要。

于 2012-04-09T00:43:42.627 回答
0

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

private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) {
    if(InvokeRequired) {
        BeginInvoke(new MethodInvoker(() => {
            totalReceivedBytes += receivedBytes;
            Label.Text = totalReceivedBytes.ToString("##,0");
        }));
    }
}
于 2012-04-09T00:45:37.090 回答