使用线程冻结 UI

Freeze UI with Threads

我有一个计时器来制作线程:

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    'ListBox2.Items.Add("Hilo")
    hiloCertificador1 = New Thread(AddressOf crearObjeto1)
    hiloCertificador1.IsBackground = True
    hiloCertificador1.Start()
End Sub
Public Sub crearObjeto1()
    UpdateList()
End Sub
Private Delegate Sub UpdateListDelegate()
Private Sub UpdateList()
    If Me.InvokeRequired Then
        Me.BeginInvoke(New UpdateListDelegate(AddressOf UpdateList))
    Else
        Dim conn As New SqlConnection(parametrosCon)
        Dim cmd = New SqlCommand("SELECT TOP 1 * FROM COLA WHERE docentry < 8654 and enviado = 0", conn)
        Dim da As New SqlClient.SqlDataAdapter(cmd)
        cmd.Connection.Open()
        da.SelectCommand = cmd
        da.Fill(dataSet, "Query")
        For Each fila As DataRow In dataSet.Tables(0).Rows
            cmd = New SqlCommand("UPDATE COLA SET enviado = 1 WHERE DOCENTRY = (@docEntry)  AND TIPO = (@tipodoc)", conn)
            cmd.Parameters.AddWithValue("@docEntry", fila("docentry"))
            cmd.Parameters.AddWithValue("@tipodoc", fila("tipo"))
            cmd.ExecuteNonQuery()
            Dim factura As New FacturaCerificacion(fila("docentry"), fila("tipo"))
        Next
        cmd.Connection.Close()
        dataSet.Tables("Query").Clear()
    End If
End Sub

定时器有一个 4000 间隔,但是当一个线程开始冻结我的 UI,我认为是因为进程太大或查询但我需要在不冻结的情况下完成它。

评论是正确的,我会为你描述其中提示的问题

使用 System.Windows.Forms.Timer

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

这将 运行 在 UI 线程上,并且只适用于做 UI 事情(即便如此,我不确定你是否真的能为它提出理由System.Threading.Timer)

创建一个新的 System.Threading.Thread

hiloCertificador1 = New Thread(AddressOf crearObjeto1)
hiloCertificador1.IsBackground = True
hiloCertificador1.Start()

现在 运行 关闭 UI,并且是 Timer.Tick 的全部内容。因此,您已勾选 UI,然后在 UI 之外创建了一个新线程。这很奇怪

调用Sub调用Sub

Public Sub crearObjeto1()
    UpdateList()
End Sub
Private Sub UpdateList()
    ' do stuff
End Sub

冗余应该是不言而喻的

做非UI的事情但遵循Control.InvokeRequired/BeginInvoke模式

Private Delegate Sub UpdateListDelegate()
Private Sub UpdateList()
    If Me.InvokeRequired Then
        Me.BeginInvoke(New UpdateListDelegate(AddressOf UpdateList))
    Else
        ' looks like a bunch of non-UI stuff
    End If
End Sub

此模式用于在 UI 上执行操作,但该块中似乎没有 UI 代码。

不使用 Using 以确保正确处理 IDisposable 个对象

Dim conn As New SqlConnection(parametrosCon)
Dim cmd = New SqlCommand("SELECT TOP 1 * FROM COLA WHERE docentry < 8654 and enviado = 0", conn)
Dim da As New SqlClient.SqlDataAdapter(cmd)
' do stuff
cmd.Connection.Close()
DataSet.Tables("Query").Clear()

与您当前的问题无关,但也很重要。

解决方案

因此,虽然这看起来是一项崇高的努力,但您似乎在 UI 之间来回走动,而且并非无缘无故,或者更准确地说,制造了 none 存在的问题一些过度工程。整个事情可以通过一些小的改变来简化

使用System.Threading.Timer

Dim Timer2 As New System.Threading.Timer(Sub() UpdateList(), Nothing, -1, -1)

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    ' done in Form_Load if your Timer1 is Enabled in designer
    ' or can be done in a Button.Click, or however you had enabled Timer1
    Timer2.Change(2000, 4000) ' this will enable Timer after 2 seconds, then will tick every 4 seconds
    'Timer2.Change(-1, -1) ' this is how it's disabled
End Sub

只需调用此方法,并使用Using 正确处理您的数据库对象。添加了 Sub DoUiStuff() 模式的正确实现方式

Private Sub UpdateList()
    Timer2.Change(-1, -1)
    Using conn As New SqlConnection(parametrosCon)
        conn.Open()
        Using cmd = New SqlCommand("SELECT TOP 1 * FROM COLA WHERE docentry < 8654 and enviado = 0", conn)
            Using da As New SqlClient.SqlDataAdapter(cmd)
                da.SelectCommand = cmd
                da.Fill(DataSet, "Query")
                For Each fila As DataRow In DataSet.Tables(0).Rows
                    Using cmdInner = New SqlCommand("UPDATE COLA SET enviado = 1 WHERE DOCENTRY = (@docEntry)  AND TIPO = (@tipodoc)", conn)
                        cmd.Connection.Open()
                        cmd.Parameters.AddWithValue("@docEntry", fila("docentry"))
                        cmd.Parameters.AddWithValue("@tipodoc", fila("tipo"))
                        cmd.ExecuteNonQuery()
                        Dim factura As New FacturaCerificacion(fila("docentry"), fila("tipo"))
                    End Using
                Next
            End Using
        End Using
        DoUiStuff(arguments) ' for example, if you need to update a GridView
        DataSet.Tables("Query").Clear()
    End Using
End Sub

Private Sub DoUiStuff(arguments As Whatever)
    If Me.InvokeRequired() Then
        Me.Invoke(New Action(Of Whatever)(AddressOf DoUiStuff), arguments)
    Else
        ' do UI stuff with arguments
    End If
    Timer2.Change(2000, -1)
End Sub

最后,为了不自相矛盾,我将添加 Dispose 方法来处理 Timer。默认情况下,此 Sub 将位于您的 Form.Designer.vb 文件中,您可以将其保留在那里或在添加后将其移至 Form.vb。

'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()>
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    Try
        If disposing Then
            components?.Dispose()
            Timer2?.Dispose()
        End If
    Finally
        MyBase.Dispose(disposing)
    End Try
End Sub