4

我的SqlDependency工作正常,当应用程序退出时,代理队列和服务会被正确删除(我在终止进程之前确实按照建议执行SqlDependency.Stop(...)),但我注意到由SqlDependency创建的通知订阅仍然存在应用程序关闭后在“sys.dm_qn_subscriptions”表中打开。

如果我稍后(应用程序关闭后)执行应该使此订阅触发的条件,它似乎会触发,因为 SQL Server 在事件查看器中记录了一条信息消息,大意是:

'{3F03B693-C0A5-E211-A97B-E06995EBDB20}.'由于以下错误,对话句柄上的查询通知对话框 已关闭: '<?xml version="1.0"?><Error xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/Error"><Code>-8490</Code><Description>Cannot find the remote service &apos;SqlQueryNotificationService-0ea1f686-e554-4e25-aa7d-4f6d85171cc3&apos; because it does not exist.</Description></Error>'

然后从“sys.dm_qn_subscriptions”中删除订阅。

注意:当应用程序处于活动状态时,订阅也会正确触发。就我的应用程序而言,没有任何问题,但让我担心的是,一旦它们所依赖的代理队列/服务终止,订阅就不会在数据库系统表中自动擦除。这可能导致(至少)在数据库中积累大量幻影/不死订阅记录,并在事件查看器中产生不必要的 SQL Server 清理消息(每个应用程序运行都会在“sys.dm_qn_subscriptions”中生成新的不死记录)。

这种行为正常吗?可以让事情变得更整洁吗?

4

2 回答 2

4

这是正常行为。QN 的寿命很长,它们会在数据库重新启动时触发(因此也会在服务器重新启动后触发)。但是SqlDependency设置了一个临时服务/队列来接收通知,并且这些应该在崩溃的情况下通过使用对话计时器内部激活来拆除。这两种机制交互的方式就是你所看到的,即 ERRORLOG 污染。没有什么不好的事情发生,至少通常不会发生,但显然不整洁。

可以让事情变得更整洁吗?

SqlNotificationRequest您可以直接使用不再提供创建服务/队列以接收您的 appdomain 通知并将它们路由到适当SqlDependency.OnChange事件的“服务”来推出您自己的解决方案。有可行的替代方案,具体取决于具体情况。但这是相当低级的工作,您最终可能会以比原始SqlDependency解决方案更糟糕的方式解决问题......

顺便说一句,无法在应用程序退出时“删除”挂起的 QN 订阅。该问题是 QN 用作通知传递机制的单向对话所固有的。适当的通知(订阅)应该由订阅者发起,并且通知应该是从目标(通知者)返回给发起者(订阅者)的响应消息。

于 2013-04-15T14:05:59.280 回答
0

如果你不介意有点俗气,我已经找到了一种在退出时清理这些的方法......

首先,设置一个 onDependencyChange 可以观察到的标志,让它知道不要重新订阅查询。

其次,设置标志并执行你知道会触发依赖订阅的无操作更新。

 update foo_master set foo_bar = foo_bar where foo_id = @id;

我的依赖监控是在单独的行上完成的,所以我只需要搔痒一排就可以触发它。这可能不是您想要在大型结果集上执行的操作。

在我的 FormClosing 事件中,我在断开连接之前触发了每个依赖项。

部分代码:

Private _dependency As SqlDependency = Nothing
Private _beingKilled = False

' dependency is set up in loadRecord(ByVal idRow as Integer)

Private Sub onDependencyChange(ByVal sender As Object, ByVal e As SqlNotificationEventArgs)
    ' This event may occur on a thread pool thread; It is illegal to update the UI from a worker thread.
    ' The following code checks to see if it is safe update the UI.
    Dim iSync As ISynchronizeInvoke = CType(_connection.masterForm, ISynchronizeInvoke)

    ' If InvokeRequired returns True, the code is executing on a worker thread.
    If iSync.InvokeRequired Then
        Dim tempDelegate As New OnChangeEventHandler(AddressOf onDependencyChange) ' Create a delegate to perform the thread switch
        Dim args() As Object = {sender, e}
        iSync.BeginInvoke(tempDelegate, args) ' Marshal the data from the worker thread to the UI thread.
    Else
        RemoveHandler _dependency.OnChange, AddressOf onDependencyChange
        If Not _beingKilled Then loadRecord(_id)
    End If
End Sub

然后只需将 _beingKilled 设置为 True 并执行无操作更新。

于 2015-02-12T04:48:15.690 回答