【问题标题】:Freeze UI with Threads用线程冻结 UI
【发布时间】:2021-12-21 08:44:10
【问题描述】:

我有一个计时器来做一个线程:

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 时,我认为是因为进程太大或查询但我需要让它不冻结。

【问题讨论】:

  • 你知道InvokeRequired/BeginInvoke做什么吗?他们确保您回到 UI 线程。从这段代码中不清楚,如果有的话,它实际上应该尝试回到 UI 线程。不太可能是在运行查询和处理结果的同时,但更清楚的是,它不应该是你做的第一件事

标签: sql vb.net


【解决方案1】:

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

使用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 之间来回切换,而且根本不是无缘无故的,或者更准确地说,是通过一些过度设计来制造不存在的问题。整个事情可以通过一些小的改动来简化

使用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

【讨论】:

  • 现在我有一个错误并且线程在不同的线程中使用相同的查询,在我遇到这个错误之前但是使用 thread.start() 它每 4 秒启动一个不同的线程和不同的查询.
  • 在您的数据库中正确设置索引。超过 4 秒的执行时间将单个记录指向全表扫描的方向...
  • 我有 4 秒的时间,因为 UI 没有完全冻结而不是查询的时间越少,如果可以的话,我可以每秒设置一次计时器,但我在 4 秒内设置了 ux
  • @MarioBraham 检查Sub UpdateList 在我的回答中,我在进入潜艇时添加了计时器停止,退出时计时器开始。此方法确保计时器在处理时不会再次滴答作响,并在完成后运行 2 秒。
  • 问题是在这种情况下我需要在不停止计时器的情况下制作程序,因为我需要使 3 或 4 个线程异步,我将尝试使用后台工作人员并在内部使用更多的计时器线程线程。我脑子里有个迷宫,对不起
猜你喜欢
  • 2011-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多