我的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 'SqlQueryNotificationService-0ea1f686-e554-4e25-aa7d-4f6d85171cc3' because it does not exist.</Description></Error>'
.
,然后从"sys.dm_qn_subscriptions"中删除订阅。
注意:当应用程序处于活动状态时,订阅也会正确触发。就我的应用程序而言,一切正常,但让我担心的是,一旦它们所依赖的代理队列/服务终止,订阅不会在数据库系统表中自动清除。这可能导致(至少)数据库中积累了大量的phantom/undead订阅记录,并在事件查看器中产生不必要的SQL Server清理消息(每个应用程序运行都会在"sys.dm_qn_subscriptions"中生成新的undead记录)。
此行为是否正常?事情能变得更整洁吗?
这是正常行为。QN是长期存在的,它们将在数据库重启时触发(因此也将在服务器重启后触发)。但是SqlDependency
设置了一个临时服务/队列来接收通知,这些应该在使用对话计时器和内部激活崩溃的情况下被拆除。这两种机制的交互方式就是您所看到的ERRORLOG污染。没有什么不好的发生,至少不经常发生,但显然不整洁。
事情可以变得更整洁吗?
你可以直接使用SqlNotificationRequest
来滚动你自己的解决方案,它不再提供创建服务/队列来接收你的appdomain通知并将它们路由到适当的SqlDependency.OnChange
事件的"服务"。根据具体情况,也有可行的替代方案。但这是相当低层次的工作,你可能最终以比原来的SqlDependency
解决方案更糟糕的方式解决问题…
顺便说一句,没有办法在应用程序退出时"删除"挂起的QN订阅。这个问题是QN用作通知传递机制的单向对话框所固有的。适当的通知(订阅)应该由订阅者发起,并且通知应该是从目标(通知者)返回到发起者(订阅者)的响应消息。
如果你不介意有点放肆的话,我已经找到了一种方法来清理这些出口…
首先,设置一个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并执行不做任何更新