How do I properly update the UI when transferring packets in C #?

I have this form which spawns a new stream and starts listening and waiting for UDP packets in a loop. I need the UI to update with the number of bytes received.

To do this, I set up an event that I will raise as soon as the packet is received and will pass the number of bytes received as an argument. Since I am not running on the UI thread, I cannot just update the UI directly. Here's what I'm doing now:

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

      

But it still runs in the same thread as the packet receiving loop , and it will not return to that loop - and wait for the next packet - until this method EVENTHANDLER_UpdateTransferProgress

returns.

My question is mainly about the next line in the method above:

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

      

Updating the user interface as it slows down the reception of packets. If I disable this line (or comment it out), receiving packets will be much faster.

How can I solve this problem? I think more threads is key, but I'm not sure how to properly implement them in this situation ... I am using Windows Forms with .NET 2.0.

EDIT:

In my previous testing, the above seems to be true, and in fact it may be to some extent. But after a bit of testing, I realized that the problem was overall Invoke(new MethodInvoker(() => { ... }));

. When I remove that (the UI won't update of course) and leave it EVENTHANDLER_UpdateTransferProgress

, but keep raising the event, receiving packets is much faster.

I have tested getting some file that took about ~ 1.5 sec on average with no call Invoke()

at all in an event handler. When I called Invoke()

in the event handler, even without updating any control in the UI, or performing any operation (in other words, the body of the anonymous method was empty), it took much longer, about ~ 5.5 sec. You can see that this is a big difference.

Is there a way to improve this?

+2


source to share


3 answers


The problem with your approach is that it updates the UI for every single package. If you were receiving 1000 packets every second, you were updating the UI 1000 times a second! The monitor is probably not refreshed more than 100 times per second, and no one will be able to read it if it refreshes more than 10 times per second.



The best way to approach this problem is to put totalReceivedBytes += receivedBytes;

on a thread that handles I / O and put a timer on a UI thread that Label.Text = totalReceivedBytes.ToString("##,0");

only executes a few times per second. When the transfer starts, start the timer; when transmission stops, stop the timer.

+3


source


Yes, there is a way to improve this.

First, use BeginInvoke

instead Invoke

that won't wait for the call to return. You should also consider using a different form in your method

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

      

Therefore, if you call this method from a method that does not need to be called, the GUI update is still in progress.




Another option you can do is to interrupt the stream in the download stream. Something like

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

      

The overhead is small compared to the benefits. GUI updates do not affect your download flow (at least not compared to waiting for a GUI update). The downside is that you are taking up a thread in the threadpool, but hey there they are! And when this is done the thread is disconnected as you are setting the full flag. You also don't need to block when setting up, as the extra mileage in the worker thread doesn't matter in context.

+1


source


Have you tried using BeginInvoke instead of Invoke? BeginInvoke () is an asynchronous call.

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

      

0


source







All Articles