我尝试实施异步搜索"引擎",但我遇到了一些困难。
出于某种原因,每隔一段时间就会抛出SqlException,说明:
"变量名称' @ input'已经宣布。变量名在查询批处理或存储过程中必须是唯一的。"
示例应用
以下代码以sys.messages表为目标,因此您所要做的就是更改连接字符串。
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.input = New TextBox() With {.Dock = DockStyle.Top, .TabIndex = 0}
Me.output = New RichTextBox() With {.Dock = DockStyle.Fill, .TabIndex = 1, .ReadOnly = True, .WordWrap = False}
Me.Controls.AddRange({Me.output, Me.input})
End Sub
Private Sub Search(sender As Object, e As EventArgs) Handles input.TextChanged
Dim input As String = Me.input.Text
Static command As SqlCommand
Static source As CancellationTokenSource
If (Not command Is Nothing) Then command.Cancel()
If (Not source Is Nothing) Then source.Cancel()
command = New SqlCommand()
source = New CancellationTokenSource()
Task.Factory.StartNew(Sub() Me.SearchingAsync(input, command, source.Token))
End Sub
Private Sub SearchingAsync(input As String, command As SqlCommand, token As CancellationToken)
Dim [error] As Exception = Nothing
Dim cancelled As Boolean = False
Dim result As List(Of sys_message) = Nothing
Try
Using connection As New SqlConnection("Server=instance\name;Database=name;Trusted_Connection=True;")
connection.Open()
command.Connection = connection
command.CommandType = CommandType.Text
command.CommandText = "select * from sys.messages where [text] like '%' + @input + '%';"
command.Parameters.AddWithValue("@input", input)
Using reader As SqlDataReader = command.ExecuteReader()
result = New List(Of sys_message)()
Do While (reader.Read() AndAlso (Not token.IsCancellationRequested))
result.Add(New sys_message() With {
.message_id = CInt(reader.Item("message_id")),
.language_id = CInt(reader.Item("language_id")),
.severity = CInt(reader.Item("severity")),
.is_event_logged = CBool(reader.Item("is_event_logged")),
.text = CStr(reader.Item("text"))
})
Loop
End Using
End Using
cancelled = token.IsCancellationRequested
Catch ex As SqlException When ex.Message.ToLower().Contains("operation cancelled by user")
cancelled = True
Catch ex As ThreadAbortException
cancelled = True
Catch ex As OperationCanceledException
cancelled = True
Catch ex As Exception
[error] = ex
Finally
Me.Invoke(
Sub()
'If (String.CompareOrdinal(input, Me.input.Text) = 0) Then
If (Not [error] Is Nothing) Then
Me.output.Text = String.Concat("Input='", input, "', Output={Result: 'error', Type: '", [error].GetType.Name, "', Message: '", [error].Message.Replace(Environment.NewLine, " "), "'}", Environment.NewLine, Me.output.Text).Trim()
ElseIf (cancelled) Then
Me.output.Text = String.Concat("Input='", input, "', Output={Result: 'cancelled'}", Environment.NewLine, Me.output.Text).Trim()
Else
Me.output.Text = String.Concat("Input='", input, "', Output={Result: 'success', Count: ", result.Count, "}", Environment.NewLine, Me.output.Text).Trim()
End If
'End If
End Sub
)
End Try
End Sub
Private WithEvents input As TextBox
Private WithEvents output As RichTextBox
Private Class sys_message
Public message_id As Integer
Public language_id As Integer
Public severity As Integer
Public is_event_logged As Boolean
Public text As String
End Class
End Class
答案 0 :(得分:1)
因为您在多次调用SqlCommand
之间无意中共享了SearchingAsync
个对象。我将删除尝试取消尝试的外部代码,并让SearchingAsync
创建自己的非共享实例。
同时,您可能需要考虑使用SqlCommand
公开的异步API,例如ExecuteReaderAsync
允许您将取消令牌传递给他们,以便取消全部由您的单一取消令牌处理。
您还需要确保将正确的取消令牌传递给方法的正确调用:
Private Sub Search(sender As Object, e As EventArgs) Handles input.TextChanged
Dim input As String = Me.input.Text
Static source As CancellationTokenSource
If (Not source Is Nothing) Then source.Cancel()
source = New CancellationTokenSource()
Dim token = source.Token
Task.Factory.StartNew(Sub() Me.SearchingAsync(input, token))
End Sub
基本上,在这行代码中:
Task.Factory.StartNew(Sub() Me.SearchingAsync(input, command, source.Token))
当它完成时,所有你知道的是,在未来的某个时间点,它将会运行:
Me.SearchingAsync(input, command, source.Token)
在未来的某个时间点,它将从SqlCommand
变量加载command
对象,然后调用SearchingAsync
(类似地,source
加载到Search
这一点)
但是,如果在此期间你的command
方法再次运行怎么办?它取消了最初用于此方法调用的source
和SearchingAsync
,并将其替换为新副本。并且计划另一个任务在将来运行command
。这两个调用最终会引用相同的@input
个对象,因此最终会将{{1}}参数添加到其中两次。