0

実行中の Windows サービスから挿入されたいくつかのアクションのログ ファイルを表すテーブルが SQL Server データベースにあります。すべてがうまくいっています。

しかし、ログ テーブルに挿入された最新の行を取得し、DataGridView. このアプリケーションを開発している間、私は MSDNの Windows アプリケーションでの SqlDependency の使用に依存していました。正常に動作していますが、ログ テーブルが大量のログの詳細を受信すると、Windows アプリがハングアップし、メイン スレッド プールがビジー状態になります。

ThreadクラスまたはBackgroundWorkerコントロールを使用して、別のスレッド プールで前のリンクで参照されている同じコードを実行したいと考えています。これは、UI コントロールを使用するためのスレッドと、データベースの変更をリッスンして に取得するための別のスレッドを意味しますDataGridView

このリンク「UI」からUIスクリーンショットを見ることができます

番号 (1): この GroupBox は、ユーザーが監視中に使用できる UI ツールを表します。

No. (2): [開始] ボタンは、データベースからの更新のリッスンと受信を開始し、DataGridView を補充する役割を果たします。

No. (3): このグリッドは、データベースに挿入された新しいログを表します。

番号 (4): この番号 (38 の変更) は、データベースの変更に対する SQL 依存関係のリッスンの数を表します。

私のコード: public partial class frmMain : Form { SqlConnection conn;

    const string tableName = "OutgoingLog";
    const string statusMessage = "{0} changes have occurred.";
    int changeCount = 0;

    private static DataSet dataToWatch = null;
    private static SqlConnection connection = null;
    private static SqlCommand command = null;

    public frmMain()
    {
        InitializeComponent();
    }

    private bool CanRequestNotifications()
    {
        // In order to use the callback feature of the
        // SqlDependency, the application must have
        // the SqlClientPermission permission.
        try
        {
            SqlClientPermission perm = new SqlClientPermission(PermissionState.Unrestricted);

            perm.Demand();

            return true;
        }
        catch
        {
            return false;
        }
    }

    private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
    {
        // This event will occur on a thread pool thread.
        // Updating the UI from a worker thread is not permitted.
        // The following code checks to see if it is safe to
        // update the UI.
        ISynchronizeInvoke i = (ISynchronizeInvoke)this;

        // If InvokeRequired returns True, the code
        // is executing on a worker thread.
        if (i.InvokeRequired)
        {
            // Create a delegate to perform the thread switch.
            OnChangeEventHandler tempDelegate = new OnChangeEventHandler(dependency_OnChange);

            object[] args = { sender, e };

            // Marshal the data from the worker thread
            // to the UI thread.
            i.BeginInvoke(tempDelegate, args);

            return;
        }

        // Remove the handler, since it is only good
        // for a single notification.
        SqlDependency dependency = (SqlDependency)sender;

        dependency.OnChange -= dependency_OnChange;

        // At this point, the code is executing on the
        // UI thread, so it is safe to update the UI.
        ++changeCount;
        lblChanges.Text = String.Format(statusMessage, changeCount);
        this.Refresh();

        // Reload the dataset that is bound to the grid.
        GetData();
    }

    private void GetData()
    {
        // Empty the dataset so that there is only
        // one batch of data displayed.
        dataToWatch.Clear();

        // Make sure the command object does not already have
        // a notification object associated with it.
        command.Notification = null;

        // Create and bind the SqlDependency object
        // to the command object.
        SqlDependency dependency = new SqlDependency(command);

        dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);

        using (SqlDataAdapter adapter = new SqlDataAdapter(command))
        {
            adapter.Fill(dataToWatch, tableName);

            dgv.DataSource = dataToWatch;
            dgv.DataMember = tableName;
            dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
        }
    }

    private void btnStart_Click(object sender, EventArgs e)
    {
        changeCount = 0;
        lblChanges.Text = String.Format(statusMessage, changeCount);

        // Remove any existing dependency connection, then create a new one.
        SqlDependency.Stop("<my connection string>");
        SqlDependency.Start("<my connection string>");

        if (connection == null)
        {
            connection = new SqlConnection("<my connection string>");
        }

        if (command == null)
        {
            command = new SqlCommand("select * from OutgoingLog", connection);
        }

        if (dataToWatch == null)
        {
            dataToWatch = new DataSet();
        }

        GetData();
    }

    private void frmMain_Load(object sender, EventArgs e)
    {
        btnStart.Enabled = CanRequestNotifications();
    }

    private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
    {
        SqlDependency.Stop("<my connection string>");
    }
}

まさに欲しいもの: ユーザーが [開始] ボタンをクリックすると、アプリケーションは別のスレッド プールでコードを実行します。

4

1 に答える 1

1

最初に、変更通知が別のスレッドで既に実行されていることをよく理解していれば、もう1つのスレッドレイヤーを使用しても意味がありません。

実際、アプリケーションがハングする原因は、UI スレッドでの UI の更新です。

この更新を担当するコードを表示していただけますか?

多くの通知がある場合、視覚的な更新は長くなり、多くのことはできません:

  • チャンクごとにグリッドを更新して更新をスムーズにします: 1000 個の新しいレコードを挿入する代わりに、100 個のレコードの更新を 10 回実行しますが、十分な速度で処理しないとデータに圧倒されるリスクがあります

  • BindingListのように通知をネイティブに処理するコレクションを使用すると役立つ場合があります

さらに、不快な「ハング」効果を回避して、ユーザー エクスペリエンスを向上させるためにできることは、プログレス バーまたは単純なスピナーを表示することです。

アップデート:

したがって、GetData 関数の最初の部分がボトルネックである場合、実際には別のスレッドを使用できます (たとえば、スレッド プールから)。

private void GetData()
{
    // Start the retrieval of data on another thread to let the UI thread free
    ThreadPool.QueueUserWorkItem(o =>
    {
        // Empty the dataset so that there is only
        // one batch of data displayed.
        dataToWatch.Clear();

        // Make sure the command object does not already have
        // a notification object associated with it.
        command.Notification = null;

        // Create and bind the SqlDependency object
        // to the command object.
        SqlDependency dependency = new SqlDependency(command);

        dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);

        using (SqlDataAdapter adapter = new SqlDataAdapter(command))
        {
            adapter.Fill(dataToWatch, tableName);

            // Update the UI
            dgv.Invoke(() =>
                {
                    dgv.DataSource = dataToWatch;
                    dgv.DataMember = tableName;
                    dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
                });
        }
    });
}

したがって、UI スレッドで実行される唯一の部分は、データグリッドの更新です。

テストされていませんが、これが役立つことを願っています...

過去?アップデート:

同時実行を回避するためにいくつかの同期を使用します。

AutoResetEvent running = new AutoResetEvent(true);

private void GetData()
{
    // Start the retrieval of data on another thread to let the UI thread free
    ThreadPool.QueueUserWorkItem(o =>
    {
        running.WaitOne();

        // Empty the dataset so that there is only
        // one batch of data displayed.
        dataToWatch.Clear();

        // Make sure the command object does not already have
        // a notification object associated with it.
        command.Notification = null;

        // Create and bind the SqlDependency object
        // to the command object.
        SqlDependency dependency = new SqlDependency(command);

        dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);

        using (SqlDataAdapter adapter = new SqlDataAdapter(command))
        {
            adapter.Fill(dataToWatch, tableName);

            running.Set();

            // Update the UI
            dgv.Invoke(() =>
                {
                dgv.DataSource = dataToWatch;
                dgv.DataMember = tableName;
                dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
                });
        }
    });
}
于 2013-06-11T08:47:01.457 に答える