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、イベントを発生させ続けると、パケットの受信がはるかに高速になります。

Invoke()イベント ハンドラをまったく呼び出さずに、平均で約 1.5 秒かかるファイルの受信をテストしました。UI のコントロールを更新したり操作を行ったりしなくても (つまり、匿名メソッドの本体が空だった場合)、イベント ハンドラーを呼び出すInvoke()と、約 5.5 秒と、はるかに長い時間がかかりました。大きな違いであることがわかります。

これを改善する方法はありますか?

4

3 に答える 3

3

あなたのアプローチの問題は、すべてのパケットで UI を更新することです。毎秒 1000 パケットを受信した場合、UI は毎秒 1000 回更新されます。モニターはおそらく 1 秒あたり 100 回を超えて更新されることはなく、1 秒あたり 10 回を超えて更新されると、誰もそれを読み取ることができなくなります。

この問題に対処するより良い方法は、I/O を処理するスレッドに を配置し、 1 秒あたり最大でも数回しかtotalReceivedBytes += receivedBytes;実行しない UI スレッドにタイマーを配置することです。Label.Text = totalReceivedBytes.ToString("##,0");転送が開始したら、タイマーを開始します。転送が停止したら、タイマーを停止します。

于 2012-04-09T01:48:17.640 に答える
1

はい、これを改善する方法があります。

1 つ目は、呼び出しが戻るのを待たずに、BeginInvoke代わりにwhichを使用することです。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 の更新を待つ場合と比較して)。欠点は、スレッドプール内のスレッドを占有していることですが、それが彼らの目的です! そして、完了するとスレッドはシャットダウンします。これは、完了フラグを設定したためです。ワーカースレッドでの余分な実行はコンテキストでは重要ではないため、それを設定するときにロックする必要はありません。

于 2012-04-09T00:43:42.627 に答える
0

Invoke の代わりに BeginInvoke を使用してみましたか? 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 に答える