0

我在 SQL Server 数据库中有一个表,它表示从正在运行的 Windows 服务插入的一些操作的日志文件。一切运作良好。

但是,我有一个 Windows 应用程序,它获取已插入日志表的最新行并在DataGridView. 在开发这个应用程序时,我依赖于在MSDN的 Windows 应用程序中使用 SqlDependency 。它运行良好,但是当日志表接收到大量日志详细信息时,Windows 应用程序挂起,主线程池变得太忙。

我想通过使用Thread类或BackgroundWorker控件在单独的线程池中运行上一个链接中引用的相同代码。这意味着一个线程用于使用 UI 控件,另一个线程用于侦听数据库更改并将其放入DataGridView.

您可以从此链接“UI”查看 UI 屏幕截图

No. (1):这个 GroupBox 代表了用户在监控时可以使用的 UI 工具。

编号(2):开始按钮负责开始监听和接收来自数据库的更新并重新填充DataGridView。

No. (3):这个网格代表已经插入数据库的新日志。

No. (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

首先,如果我理解得很好,更改通知已经在另一个线程上执行,所以再使用一个线程层应该是没用的。

实际上,导致应用程序挂起的原因是 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 回答