3

私のアプリは現在それを行っているため、Invoke/BeginInvoke を使用せずにワーカー スレッドから GUI コントロールを読み取ることができることはわかっています。クロス スレッド例外エラーはスローされず、私の System.Timers.Timer スレッドは GUI コントロール値を正常に読み取ることができます (この男とは異なり:ワーカー スレッドは GUI でコントロールを読み取ることができますか? )

質問 1: スレッドの基本ルールを考えると、Invoke/BeginInvoke を使用してフォーム コントロールの値を読み取る必要がありますか? そして、これによりスレッドセーフになりますか? この質問の背景は、私のアプリが抱えている問題に由来しています。別のスレッドが参照しているフォーム コントロールがランダムに破損しているようです。(質問 2 を参照)

質問 2: フォーム コントロールの値を更新する必要がある 2 番目のスレッドがあるため、これらの値を更新するために Invoke/BeginInvoke を実行します。この同じスレッドは、それらのコントロールを更新できるように、これらのコントロールへの参照を必要とします。これらのコントロールのリストを保持します (DataGridViewRow オブジェクトなど)。場合によっては (常にではありません)、DataGridViewRow 参照が「破損」することがあります。破損とは、参照はまだ有効ですが、DataGridViewRow プロパティの一部が null (例: row.Cells) であることです。これは質問 1 が原因ですか、それともなぜこのようなことが起こっているのかについて何かヒントを教えていただけますか?

ここにいくつかのコードがあります(最後の行に問題があります):

public partial class MyForm : Form
{
    void Timer_Elapsed(object sender)
    {
        // we're on a new thread (this function gets called every few seconds)  
        UpdateUiHelper updateUiHelper = new UpdateUiHelper(this);       

        // Is it thread-safe to step through the datagrid rows here without invoking?
        foreach (DataGridViewRow row in dataGridView1.Rows)
        {
            object[] values = GetValuesFromDb();
            updateUiHelper.UpdateRowValues(row, values[0]);
        }

        // .. do other work here

        updateUiHelper.UpdateUi();
    }
}

public class UpdateUiHelper
{
    private readonly Form _form;
    private Dictionary<DataGridViewRow, object> _rows;
    private delegate void RowDelegate(DataGridViewRow row);
    private readonly object _lockObject = new object();

    public UpdateUiHelper(Form form)
    {
        _form = form;
        _rows = new Dictionary<DataGridViewRow, object>();
    }

    public void UpdateRowValues(DataGridViewRow row, object value)
    {
        if (_rows.ContainsKey(row))
            _rows[row] = value;
        else
        {
            lock (_lockObject)
            {
                _rows.Add(row, value);
            }
        }

    }

    public void UpdateUi()
    {
        foreach (DataGridViewRow row in _rows.Keys)
        {
            SetRowValueThreadSafe(row);               
        }
    }

    private void SetRowValueThreadSafe(DataGridViewRow row)
    {
        if (_form.InvokeRequired)
        {
            _form.Invoke(new RowDelegate(SetRowValueThreadSafe), new object[] { row });
            return;
        }

        // now we're on the UI thread
        object newValue = _rows[row];
        row.Cells[0].Value = newValue; // randomly errors here with NullReferenceException, but row is never null!
    }
4

1 に答える 1

7

RE 1: 原則として、メッセージ ポンプが実行されているスレッドで Windows コントロールにアクセスする必要があります。.NET では、それが実行中のスレッドですApplication.Run. これはメッセージを処理するものであり、Windows コントロールに対して行うことはすべてメッセージです。そのため、別のスレッドからメッセージを送信すると、メッセージを処理するスレッドで競合状態が発生する可能性があります (メッセージ ポンプは、パフォーマンスに大きな影響を与えるなどの理由でスレッドセーフではありません)。現在、.NET クラス インスタンスにアクセスしても、必ずしもメッセージが送信されるとは限りません。そのような場合、はい、UI スレッドではないスレッドからアクセスできます。メッセージを送信するものと送信しないもの、または単一のメンバーがメッセージを送信する場合と送信しない場合については、文書化されていません。したがって、非 UI スレッドからコントロール オブジェクトにアクセスしても例外が発生しないという賭けに出ているだけです。ベスト プラクティスは、常にInvokeRequired/を使用することです。BeginInvoke. あなたがそれを期待していないときに他の何かが変わる可能性があり、それが失敗した場合、あなたがしたことはサポートされていません.

RE 2: 1 が 2 を引き起こしたかどうかはわかりません。詳細が十分ではありません。サポートされていない方法で何かを行っている場合、予期しないことが起こると予想するのは当然です。

これが WinForms アプリの場合は、Forms.Timer代わりに使用することをお勧めします。UIスレッドでハンドラーを呼び出すため、 /Tickを気にする必要はありませんInvokeRequiredBeginInvoke

于 2012-09-07T16:14:41.417 に答える