7

Linuxポートを想定してアプリをmonoでテストしていますが、スレッドに問題があります。最初はここに3000コード行を貼り付けることを検討しましたが、最後に小さな最小限の例を考案しました;)

ボタン付きのフォームがあります(詩的に名前が付けられButton1、ラベル(当然のことながら名前が付けられていますLabel1))。たくさんの人がと呼ばれる形で幸せな生活を送っていForm1ます。クリックするButton1と、ローカルカウンターをインクリメントし、その値を反映するようにLabel1(を使用して)更新する無限ループが起動します。Invoke

現在Monoでは、フォームのサイズを変更すると、ラベルの更新が停止し、再起動することはありません。これは、MSの実装では発生しません。BeginInvokeこれ以上うまくいきません。さらに悪いことに、どちらの場合もUIがハングします。

この不一致がどこから来ているのか知っていますか?どのようにそれを解決しますか?そして最後に、BeginInvokeがここで機能しないのはなぜですか?私は大きな間違いを犯しているに違いありません...しかし、どれですか?


編集:これまでのいくつかの進歩:

  • BeginInvokeを呼び出すことは、実際には機能します。ただ、UIが十分に速く更新されないため、停止しているように見えます。
  • モノラルでは、UIキューにメッセージを挿入すると(フォームのサイズを変更するなどして)、スレッド全体がハングします。実際、同期Invoke呼び出しは決して戻りません。私はその理由を理解しようとしています。
  • 興味深いことに、を使用してもBeginInvoke、サイズ変更操作が終了する前に非同期呼び出しは実行されません。MS.Netでは、サイズ変更中も実行を続けます。

コードは次のようになります(C#バージョンより低い):

Public Class Form1
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim T As New Threading.Thread(AddressOf Increment)
        T.Start()
    End Sub

    Sub UpdateLabel(ByVal Text As String)
        Label1.Text = Text
    End Sub

    Delegate Sub UpdateLabelHandler(ByVal Text As String)
    Sub Increment()
        Dim i As Long = 0
        Dim UpdateLabelDelegate As New UpdateLabelHandler(AddressOf UpdateLabel)
        Try
            While True
                i = (i + 1) Mod (Long.MaxValue - 1)
                Me.Invoke(UpdateLabelDelegate, New Object() {i.ToString})
            End While
        Catch Ex As ObjectDisposedException
        End Try
    End Sub
End Class

または、C#では、

public class Form1
{
    private void Button1_Click(System.Object sender, System.EventArgs e)
    {
        System.Threading.Thread T = new System.Threading.Thread(Increment);
        T.Start();
    }

    public void UpdateLabel(string Text)
    {
        Label1.Text = Text;
    }

    public delegate void UpdateLabelHandler(string Text);
    public void Increment()
    {
        long i = 0;
        UpdateLabelHandler UpdateLabelDelegate = new UpdateLabelHandler(UpdateLabel);
        try {
            while (true) {
                i = (i + 1) % (long.MaxValue - 1);
                this.Invoke(UpdateLabelDelegate, new object[] { i.ToString() });
            }
        } catch (ObjectDisposedException Ex) {
        }
    }
}
4

3 に答える 3

5

これはモノラルランタイムのバグですが、少なくともそうだと思います。コードは良い習慣ではないかもしれませんが(私はスレッドの専門家ではありません)、バグを示唆しているのは、WindowsとLinuxで動作が異なるという事実です。

Linuxでは、monoはMS.NetがWindowsで行うのとまったく同じ動作をします。サイズ変更中であっても、ぶら下がることなく、継続的に更新されます。

Windowsでは、monoは前述のすべての問題を表示します。https://bugzilla.novell.com/show_bug.cgi?id=690400にバグレポートを投稿しました。

于 2011-04-27T21:38:48.163 に答える
1

この不一致がどこから来ているのか知っていますか?どのようにそれを解決しますか?

私はわかりません。あなたのコードには、Monoと.NETの違いを引き起こすような明らかなものは何も見当たりません。私が大げさな推測をしなければならないとしたら、Monoのあいまいなバグに遭遇した可能性があると思います。ただし、Monoは、フォームを更新するWM_PAINTメッセージを処理するために十分に異なるメカニズムを使用している可能性があると思います。繰り返しの呼び出しによるUIスレッドの絶え間ないドキドキはInvoke、フォームを更新するMonoの機能を混乱させる可能性があります。

そして最後に、BeginInvokeがここで機能しないのはなぜですか?

Invokeタイトなループで呼び出すことは十分にBeginInvoke悪いですが、さらに悪いことになります。ワーカースレッドがUIメッセージポンプをフラッディングしています。BeginInvokeUIスレッドがデリゲートの実行を終了するまで待機しません。リクエストを投稿してすぐに戻ります。それがハングしているように見える理由です。UIメッセージキューに投稿されているメッセージBeginInvokeは、ワーカースレッドがUIスレッドの処理能力を大幅に上回っている可能性があるため、蓄積され続けます。

他のコメント

また、ワーカースレッドはコードではほとんど役に立たないことにも言及する必要があります。その理由は、Invokeすべての反復でへの呼び出しがあるためです。InvokeUIがデリゲートの実行を終了するまでブロックします。つまり、ワーカースレッドとUIスレッドは基本的に互いにロックステップになっています。つまり、ワーカーはほとんどの時間をUIの待機に費やしており、その逆も同様です。

解決

Invoke考えられる解決策の1つは、が呼び出される速度を遅くすることです。ループの反復ごとに呼び出すのではなく、1000回の反復ごとなどに実行してみてください。

さらに良いアプローチは、使用しないInvokeBeginInvoke、まったく使用しないことです。個人的には、UIを更新するためのこれらのメカニズムは使いすぎだと思います。特にワーカースレッドが継続的な処理を行っている場合は、UIスレッドに独自の更新レートを調整させることをお勧めします。これは、フォームにタイマーを配置し、希望のリフレッシュレートでチェックする必要があることを意味します。イベントからTick、ワーカースレッドが更新している共有データ構造をプローブし、その情報を使用してフォームのコントロールを更新します。これにはいくつかの利点があります。

  • それは、UIとワーカースレッドの間の緊密な結合を壊しControl.Invokeます。
  • それはとにかくそれが属するべきUIスレッドのUIスレッドを更新する責任を負います。
  • UIスレッドは、更新をいつ、どのくらいの頻度で実行するかを指示します。
  • ワーカースレッドによって開始されたマーシャリング手法の場合のように、UIメッセージポンプがオーバーランするリスクはありません。
  • ワーカースレッドは、更新が実行されたことの確認を待ってから次の手順に進む必要はありません(つまり、UIスレッドとワーカースレッドの両方でスループットが向上します)。
于 2011-04-27T17:17:32.270 に答える
0

何よりもまず、 Button1のクリックはすでに非同期であるため、インクリメントするために別のスレッドを作成する必要はありません。インクリメントメソッドを呼び出すだけです。申し訳ありませんが、質問を1行ずつ読んでいて、whileループに到達するまでにボタンを忘れました:

private void Button1_Click(System.Object sender, System.EventArgs e)
{
    Thread t = new Thread(Increment);
    t.IsBackground = true;
    t.Start();
}

2番目:スレッドを使用する必要がある場合は、フォアグラウンドスレッドを使用する正当な理由がない限り、常にスレッドをバックグラウンドに設定する必要があります(つまり、フォアグラウンドはプロセスの終了を防ぎます)。

3番目: UIを更新する場合は、InvokeRequiredプロパティを確認してBeginInvoke:を呼び出す必要があります。

public void UpdateLabel(string Text)
{

    if (InvokeRequired)
    {
        BeginInvoke(new UpdateLabelDelegate(UpdateLabel), Text);
    }
    else
    {
        Label1.Text = Text;
    }
}

public void Increment()
{
    int i = 0;
    while(true)
    {
        i++; // just incrementing i??
        UpdateLabel(i.ToString());

        Thread.Sleep(1000);// slow down a bit so you can see the updates
    }
}

InvokeRequiredの「パターン」を「自動化」することもできます。InvokeRequiredコードパターンの自動化

そして今、あなたがまだ同じ問題を抱えているかどうか見てください。

私は自分のマシンでそれを試しました、そしてそれは魅力のように働きます:

public partial class Form1 : Form
{
    private delegate void UpdateLabelDelegate(string text);
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Thread t = new Thread(Increment);
        t.IsBackground = true;
        t.Start();
    }

    private void UpdateLabel(string text)
    {
        if (label1.InvokeRequired)
        {
            BeginInvoke(new UpdateLabelDelegate(UpdateLabel), text);
        }
        else
        {
            label1.Text = text;
        }

    }

    private void Increment()
    {
        int i = 0;
        while (true)
        {
            i++;
            UpdateLabel(i.ToString());
            Thread.Sleep(1000);
        }
    }
}
于 2011-04-27T13:49:20.060 に答える