SqlDependency using BackgroundWorker

本文关键字:BackgroundWorker using SqlDependency | 更新日期: 2023-09-27 18:17:43

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

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

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

你可以从这个链接看到UI截图"UI"

。(1):此群盒代表用户在监控时可以使用的UI工具。

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

。(3):此网格表示已插入数据库的新日志。

。(4):这个数字(38次更改)表示侦听sql依赖项对数据库更改的计数。

我的代码:

公共部分类frommain: Form{SqlConnection康涅狄格州;

    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>");
    }
}

我想要的是:当用户单击Start按钮时,应用程序在一个分离的线程池中运行代码。

首先,如果我理解得好更改通知已经在另一个线程上执行,那么使用多一个线程层应该是无用的。

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

SqlDependency using BackgroundWorker

相关文章:
  • 没有找到相关文章