3

我正在编写一个简单的线程应用程序:单击开始按钮时,应用程序禁用此按钮,运行 5 个线程,只需进行 For 迭代并更新 5 个 ProgressBars。最后一个线程正在等待线程结束,以重新启用我的开始按钮。

问题:用户在进度条达到 100% 之前看到按钮已启用......并且进度条仍在更新!

有什么问题?有没有办法在 UI Thread 上做一个 join 语句?

这是代码:

Imports System.Threading

Public Structure ThreadParameters
    Public ThreadId As Integer
    Public pgBar As ProgressBar
    Public iterations As Integer
End Structure

Public Structure SetPgValueParameters
    Public pgBar As ProgressBar
    Public value As Integer
End Structure

Public Class Form1

Private threads(4) As Thread
Private Shared FormThread As Thread

Private Sub StartButton_Click(sender As Object, e As EventArgs) Handles StartButton.Click
    StartButton.Enabled = False

    Dim MainThread As Thread = New Thread(AddressOf WaitThreads)
    MainThread.Start()

    For i = 0 To 4
        threads(i) = New Thread(AddressOf ThreadRun)

        Dim params As ThreadParameters

        params.pgBar = Me.Controls.Find("ProgressBar" & (i + 1), False)(0)
        params.iterations = 100
        params.ThreadId = i

        threads(i).Start(params)
    Next

End Sub

Private Sub ThreadRun(params As ThreadParameters)
    Dim invokeParams As SetPgValueParameters
    Dim lastIntegerVal As Integer = 1
    invokeParams.pgBar = params.pgBar
    For i = 0 To params.iterations
            invokeParams.value = (100 * i / params.iterations)
        setPgValue(invokeParams)
        Console.WriteLine(params.ThreadId & ":" & i & "/" & params.iterations)
    Next
End Sub

Private Delegate Sub setPgValueDelegate(params As SetPgValueParameters)
Private Sub setPgValue(params As SetPgValueParameters)
    If params.pgBar.InvokeRequired Then
        Dim d As New setPgValueDelegate(AddressOf setPgValue)
        Me.Invoke(d, New Object() {params})
    Else
        params.pgBar.Value = params.value
        'Application.DoEvents()
    End If
End Sub


Private Sub WaitThreads()
    For i = 0 To 4
        threads(i).Join()
    Next
    FormThread.Join()
    setButtonEnabled()
End Sub

Private Delegate Sub setButtonEnabledDelegate()
Private Sub setButtonEnabled()
    If StartButton.InvokeRequired Then
        Dim d As New setButtonEnabledDelegate(AddressOf setButtonEnabled)
        Me.Invoke(d, Nothing)
    Else
        Application.DoEvents()
        StartButton.Enabled = True
        Console.WriteLine("Bouton réactivé")
    End If
End Sub
End Class

编辑:感谢链接 Bjørn-Roger Kringsjå。所以有一个解决方法:增加并直接减少值跳过动画。对于最大值,将其设置为最大值,减1,加1...仍然有一点滞后,但是确实比在ProgressBars在中间时启用按钮要好:)

4

1 回答 1

2

好的,我做了一些测试,结果发现问题出在进度条动画上。在您的按钮启用时(这是正确的),进度条仍处于“动画模式”。这可以通过禁用XP 视觉样式来验证。

进度条有一个属性名称MarqueeAnimationSpeedBlocks ,但不幸的是这在使用样式时没有效果。

我做了一个谷歌搜索,发现了这个 SO 帖子:

更改值时禁用.NET进度条动画?

示例应用程序

Imports System.Threading
Imports System.Threading.Tasks

Public Class Form1

    Public Sub New()
        Me.InitializeComponent()
        Me.StartButton = New Button() With {.TabIndex = 0, .Dock = DockStyle.Top, .Text = "Start", .Height = 30}
        Me.ProgressBar1 = New ProgressBar With {.TabIndex = 1, .Dock = DockStyle.Top, .Height = 30, .Name = "ProgressBar1"}
        Me.ProgressBar2 = New ProgressBar With {.TabIndex = 2, .Dock = DockStyle.Top, .Height = 30, .Name = "ProgressBar2"}
        Me.ProgressBar3 = New ProgressBar With {.TabIndex = 3, .Dock = DockStyle.Top, .Height = 30, .Name = "ProgressBar3"}
        Me.ProgressBar4 = New ProgressBar With {.TabIndex = 4, .Dock = DockStyle.Top, .Height = 30, .Name = "ProgressBar4"}
        Me.ProgressBar5 = New ProgressBar With {.TabIndex = 5, .Dock = DockStyle.Top, .Height = 30, .Name = "ProgressBar5"}
        Me.Controls.AddRange({Me.ProgressBar5, Me.ProgressBar4, Me.ProgressBar3, Me.ProgressBar2, Me.ProgressBar1, Me.StartButton})
    End Sub

    Private Sub HandleStartButtonClicked(sender As Object, e As EventArgs) Handles StartButton.Click

        Me.StartButton.Enabled = False

        Dim tasks As Task() = New Task(4) {}

        For i As Integer = 0 To 4
            Dim index As Integer = i
            tasks(i) = New Task(
                Sub()
                    Dim n As Integer = rnd.Next(100, 1001)
                    For j As Integer = 0 To n
                        Me.Invoke(
                            Sub()
                                DirectCast(Me.Controls("ProgressBar" & (index + 1).ToString()), ProgressBar).Value = CInt((j / n) * 100.0#)
                            End Sub
                        )
                        Thread.Sleep(10)
                    Next
                End Sub
                )
        Next

        Task.Factory.ContinueWhenAll(tasks,
            Sub() Me.Invoke(
                Sub()
                    MessageBox.Show(String.Join(Environment.NewLine, From pb As ProgressBar In Me.Controls.OfType(Of ProgressBar)() Select (pb.Name & ": " & pb.Value.ToString())))
                    Me.StartButton.Enabled = True
                End Sub
            )
        )

        Array.ForEach(Of Task)(tasks, Sub(t) t.Start())

    End Sub

    Private WithEvents StartButton As Button
    Private WithEvents ProgressBar1 As ProgressBar
    Private WithEvents ProgressBar2 As ProgressBar
    Private WithEvents ProgressBar3 As ProgressBar
    Private WithEvents ProgressBar4 As ProgressBar
    Private WithEvents ProgressBar5 As ProgressBar
    Private Shared rnd As New Random()

End Class
于 2014-05-15T15:42:00.203 回答