【问题标题】:SqlDependency using BackgroundWorker使用 BackgroundWorker 的 SqlDependency
【发布时间】:2013-06-10 21:16:58
【问题描述】:

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

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

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

您可以从此链接查看 UI 截图 "UI"

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

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

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

没有。 (4):这个数字(38个变化)表示监听sql依赖对数据库变化的计数。

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

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

【问题讨论】:

  • 将依赖项与例如 Backgroundworker 混合会使事情复杂化。也许考虑只使用一个。
  • 这个应用程序的有趣练习将是一个应用程序监听器订阅 win 服务和服务发送日志表更新的信号。届时win app会去获取新数据。
  • 感谢您的支持,但我不能像您说的那样将应用程序分成两部分。我必须根据前面的解释来开发它。我只需要将相同的代码运行到一个单独的 线程池 或使用 BackgroundWorker 运行它。

标签: c# multithreading backgroundworker sqldependency


【解决方案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;
                });
        }
    });
}

【讨论】:

  • 非常感谢您的支持先生。让我详细解释一下这个想法。忘记有windows服务做点什么,但记住以下几点:我有一个datagrid,sql依赖总是根据sql查询从数据库中获取行。所以,我不能在填充数据网格时使用 UI 控件,这看起来就像在您的应用程序中使用无限循环。因此,我想在单独的线程池或 BackgroundWorker 中运行链接“msdn.microsoft.com/en-us/library/a52dhwx7%28v=vs.80%29.aspx”的代码,以允许用户轻松使用 UI。你可以去链接看代码。
  • 我更新了我的问题,你可以看到代码脚本和UI截图。非常感谢。
  • 再次感谢您的热心帮助 :),但是当我使用您的代码时出现此错误 “已经有一个打开的 DataReader 与此命令关联,必须先关闭。”,虽然我没有数据读取器并且我在连接字符串中使用 "MultipleActiveResultSets=True" 以避免这些错误。
  • 嗯,这可能是由于 2 GetData 同时运行。我已经更新了我的答案。希望这次一切都会好起来的……
  • 非常感谢您的支持,这是在一个单独的线程中运行的主线程池。但是,现在的问题是,尽管数据表有行,但 Grid 没有填充数据。你怎么看?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多