问题
I'm coding a simple thread application: when clicking a start button, the application disable this button, run 5 threads simply making For iterations and updating 5 ProgressBars. A last thread is waiting for the end of the threads, to re-enable my start button.
Problem: The user is seeing the button enabled before the progressbars are at 100%... And the progressbars are still updating!
What's the problem? Is there a way to make a join statement on the UI Thread?
Here is the code:
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
EDIT: Thanks for the link Bjørn-Roger Kringsjå. So there's a work-around: increase and directly dicrease the value skip the animation. For the max value, set it the value to the max, decrease by 1, and increase by 1... There still a little lag, but it's really better than having the button enabled when ProgressBars are in the middle :)
回答1:
Okay, I did some testing and it turns out that the problem lies with the progress bar animation. At the time your button gets enabled (which is correct) the progress bar is still in "animation mode". This can be verified by disabling XP visual styles.
The progress bar have a property name MarqueeAnimationSpeed, but unfortunately this have no effect when using Blocks
style.
I did a google search and found this SO post:
Disabling .NET progressbar animation when changing value?
Sample application
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
来源:https://stackoverflow.com/questions/23679542/dotnet-is-there-a-way-to-do-a-join-statement-on-the-ui-thread