使用BackgroundWorker的SqlDependency [英] SqlDependency using BackgroundWorker

查看:71
本文介绍了使用BackgroundWorker的SqlDependency的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

您可以从此链接 ="ahttp://s1.postimg.org/92p31f6fj/image.png" rel ="nofollow">"UI"

不. (1):此GroupBox代表用户可在监视时使用它的UI工具.

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

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

不. (4):此数字(38个更改)表示侦听sql依赖关系对数据库更改的计数.

我的代码: 公共局部类frmMain:表单 { 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>");
    }
}

我想要的是什么:当用户单击开始按钮时,应用程序在单独的线程池中运行代码.

解决方案

首先,如果我了解更改通知已经在另一个线程上执行,那么再使用一个线程层应该没用.

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

I have a table in a SQL Server database that represents a log file of some actions inserted from a running windows service. Everything is working well.

But, I have a Windows application that gets the latest rows that have been inserted in the log table and views it in a DataGridView. While developing this application I depended on Using SqlDependency in a Windows Application from MSDN. It is working well, but when the log table receives a large number of log details, the Windows app hangs up and the main thread pool becomes too busy.

I want to run the same code referenced in the previous link in a separated thread pool by using Thread class or BackgroundWorker control. This means a thread for using UI controls and another one for listening to the database changes and get it into the DataGridView.

You can see the UI screenshot from this link "UI"

No. (1): This GroupBox represents the UI tools which users can use it while monitoring.

No. (2): The Start button is responsible for beginning to listen and receive updates from the database and refill the DataGridView.

No. (3): This grid represents the new logs that have been inserted in the database.

No. (4): This number (38 changes) represents the count of listening of sql dependency to the database changes.

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

What I want exactly: when user click the Start button, the application run the code in a separated thread pool.

解决方案

First if I understand well the change notification is already executed on another thread so using one more threading layer should be useless.

Indeed what causes the application to hang is the update of the UI, on the UI thread.

Could you show the code responsible for this update please?

If there is a lot of notifications visual update will be longer and you can't do much:

  • update the grid by chunks to smooth the update: instead of inserting 1000 new records you run 10 updates of 100 records, but you take the risk of being overwhelmed by data if you don't process them fast enough

  • using a collection that handles notification natively like a BindingList could help

Moreover what you can do to enhance the user experience, avoiding the unpleasant "it hangs" effect, is displaying a progress bar or a simple spinner.

UPDATE:

So if the first part of the GetData function is the bottleneck then indeed you could use another thread, e.g. from the thread-pool:

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

So the only part that will run on the UI thread is the update of the datagrid.

Not tested but hope this helps...

LAST? UPDATE:

With some synchronization to avoid concurrent execution:

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

这篇关于使用BackgroundWorker的SqlDependency的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆