2012-04-08 9 views
2

私は新しいスレッドを生成し、リスニングしてループ内のUDPパケットを待つこのフォームを持っています。私が必要とするのは、受け取ったバイト数でUIを更新し続けることです。C#でパケットを転送中に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フォームを使用しています。

EDIT:私の以前のテストに

、上記のは本当であるように見える、それが実際にある程度のかもしれません。しかし、もう少しテストした後、私は問題が全体でInvoke(new MethodInvoker(() => { ... }));ものであることに気づいた。それを取り除くと(UIはもちろん更新されません)、EVENTHANDLER_UpdateTransferProgressのままにしておきますが、イベントを引き上げると、パケットの受信がはるかに高速になります。

私はイベントハンドラでInvoke()を呼び出さずに平均約1.5秒かかったファイルを受け取ったことをテストしました。私がイベントハンドラでInvoke()を呼び出すと、UI内のコントロールを更新したり、何らかの操作をしなくても(換言すれば、匿名メソッド本体は空だった)、約5.5秒ほどの時間がかかりました。あなたはそれが大きな違いだと分かります。

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

+0

使用している.NETのバージョンは何ですか? –

+0

@SeanThomanこの特定のアプリケーションでは、.NET 2.0。 –

+1

このスレッドを見る - http://stackoverflow.com/questions/661561/how-to-update-gui-from-another-thread-in-c –

答えて

3

問題は、1パケットごとにUIを更新することです。毎秒1000パケットを受信した場合、毎秒1000回UIを更新します。モニターはおそらく1秒間に100回以上リフレッシュされません。また、1秒間に10回以上更新すると、誰もそれを読み取ることができません。

この問題にアプローチするより良い方法は、totalReceivedBytes += receivedBytes;をI/Oを処理するスレッドに入れて、Label.Text = totalReceivedBytes.ToString("##,0");を実行するUIスレッドにタイマーを最大で数回だけ置くことです。転送が開始されたら、タイマーを開始します。転送が停止したら、タイマーを停止します。

+0

はい、私はこの質問を投稿した後、結局同じ結論に達しました。それはおそらく多くのスピードを上げるだろう...明確にするために、あなたはWinFormsのタイマーについて話している?または、より良い選択肢がありますか?私はWinFormsタイマーにはあらゆる種類の問題があることを読んでいます...しかし、私は何らかの "ティッカー"イベントの代替案を認識していません。 –

+0

はい、これはフォームに置く標準の 'System.Windows.Forms.Timer'です。私はその問題については確信していませんが、まさにあなたがこの仕事のために欲しいものです。 – Gabe

+1

+1これは正しいアプローチです。 Invokeは呼び出しスレッドをブロックします。 BeginInvokeは高いシグナリングレートでは、GUIスレッドメッセージキューが空になるよりも速くガントをいっぱいにすることがあります。 1秒のForms.Timerが良いアプローチです。ポーリングがより良い選択肢である場合、これは数回のうちの1つです:) –

1

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

最初にInvokeの代わりにBeginInvokeを使用します。これは呼び出しが返されるのを待たないでしょう。あなたは、GUIの更新がまだ実行され起動する必要はありませんメソッドからこのメソッドを呼び出した場合にも、

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

だからあなたの方法で別のフォームを使用して検討すべきです。


別の方法として、ダウンロードスレッドのスレッドを中断することができます。

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を更新するのを待つのと比べて)。欠点は、あなたがスレッドプール内のスレッドを占有していることですが、ちょっと、彼らはそこにいるのです!完了したら、スレッドは完全なフラグをセットしてからシャットダウンします。これを設定するときにロックする必要はありません。ワーカースレッド内の余分な実行はコンテキスト内で重要ではないためです。

+0

'Invoke'の代わりに' BeginInvoke'を試してみましょう、少し助けてください。しかし、私は本当の問題は上記の@ Gabeの答えで修正されると信じています。イベントハンドラは、呼び出しを必要としないメソッドからは決して呼び出されません。少なくともこのプログラムではない。しかし、このチップのおかげで、将来的には役立つかもしれません。 –

+0

@RicardoAmaral:概念は同じです。私のバージョンでは、送信スレッドは800ミリ秒ごとに更新をプッシュします.Gabeのバージョンでは、GUIには毎秒一定の時間を更新するタイマーがあります。だから、彼らは同じことをする... – Patrick

+0

@RicardoAmaral:あなたが気づいたかどうかわからないが、 'ThreadPool.QueueUserWorkItem'は実際に別のスレッドを開始するので、あなたのダウンロードメソッドはそれ自身で実行され続ける...だから基本的には(args.Completeがtrueの時に終了する800ミリ秒の繰り返し) – Patrick

0

Invokeの代わりにBeginInvokeを使用してみましたか? BeginInvoke()は非同期呼び出しです。

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

@Patrickは既に提案しました数分早く笑)、私はそれを試して、それが助けになるかどうかを見てみるが、彼の答えにコメントしたように、正解はガベの答えだと思う。しかし、いくつかのテストを実行する必要があります。 –

関連する問題