vb.net How can I show in each loop what thread is calculating and continue working in the form? - vb.net

I have tried to make a loop inside a thread and, while it is showing me values in a textbox, continue working in my form.
The problem is, when it starts I can not do anything in the form, like pressing buttons or setting commands.
Here is the idea of code:
Imports System.Threading
Public Class Form1
Dim trd As System.Threading.Thread
Dim cont As Integer
Private Sub btnOpen_Click(sender As Object, e As EventArgs) Handles btnOpen.Click
cont = 0
trd = New Thread(AddressOf trdReadAxis)
trd.Start()
End Sub
Private Delegate Sub CloseFormCallback()
Private Sub trdReadAxis()
While cont < 10
If InvokeRequired Then
Dim d As New CloseFormCallback(AddressOf trdReadAxis)
Invoke(d, Nothing)
Else
txtXPosition.Text = cont
cont += 1
Thread.Sleep(1000)
End If
end while
end class
What can I do to see everi second the change in my textbox "txtXPosition"?
Thank you.

This code is a mess:
Private Sub trdReadAxis()
While cont < 10
If InvokeRequired Then
Dim d As New CloseFormCallback(AddressOf trdReadAxis)
Invoke(d, Nothing)
Else
txtXPosition.Text = cont
cont += 1
Thread.Sleep(1000)
End If
End while
End Sub
It looks like your worker thread calls itself back via Invoke, where it will execute on the UI thread. Once it is there, it goes through the entire while loop-- still on the UI thread-- with a Sleep(1000) in every iteration of the loop. This will lock out the message loop which is supposed to be handling your clicks and keystrokes.
I suggest you divide out the worker thread from the code that updates the textbox, like so:
Delegate SetTextCallBack(ByVal message as string)
Private Sub SetText(ByVal message as String)
if InvokedRequired then
Dim d As New SetTextCallback(AddressOf SetText)
Invoke(d, message)
Else
txtXPosition.Text = message
End If
end sub
Private Sub trdReadAxis()
While cont < 10
SetText cont
cont += 1
Thread.Sleep(1000)
End while
End Sub

Related

Multithreading doesn't work

I'm making a simple multithreading program to explain the working of threading. I want two counters counting on the same time but it doesn't work.
It only works if I use: CheckForIllegalCrossThreadCalls = False. But, I want to program in a proper way.
Code:
Dim Thread1 As System.Threading.Thread
Dim Thread2 As System.Threading.Thread
Private Delegate Sub SetTeller1()
Private Sub teller1()
If teller1Label.InvokeRequired Then
Invoke(New SetTeller1(AddressOf teller1))
Else
For i As Integer = 0 To 1000
teller1Label.Text = i
Refresh()
Next
End If
End Sub
Delegate Sub SetTeller2()
Private Sub teller2()
If teller2Label.InvokeRequired Then
Invoke(New SetTeller2(AddressOf teller2))
Else
For i As Integer = 0 To 1000
teller2Label.Text = i
Refresh()
Next
End If
End Sub
Private Sub teller1Button_Click(sender As Object, e As EventArgs) Handles teller1Button.Click
Thread1 = New Threading.Thread(AddressOf teller1)
Thread1.Start()
End Sub
Private Sub teller2Button_Click(sender As Object, e As EventArgs) Handles teller2Button.Click
Thread2 = New Threading.Thread(AddressOf teller2)
Thread2.Start()
End Sub
The multithreading works perfectly, but you are not utilizing it. The only thing you're currently doing in the background thread is calling Invoke, which means that your thread will exit within a few milliseconds and then be discarded.
Once you call Invoke the execution of the teller1 or teller2 method is moved to the UI thread, meaning it will block the UI until its execution is finished. You should only invoke when you are to update the UI, and perform all iterations in the background thread.
Here's an example of how you can do it more properly:
Delegate Sub SetTeller1(ByVal Text As String)
Private Sub teller1()
For i As Integer = 0 To 1000
SetTeller1Text(i)
Next
End Sub
Private Sub SetTeller1Text(ByVal Text As String)
If Me.InvokeRequired Then
Me.Invoke(New SetTeller1(AddressOf SetTeller1Text), Text)
Else
teller1Label.Text = Text
Me.Refresh()
End If
End Sub
For improved readability I changed for example Invoke(...) to Me.Invoke(...).
Also I'm not sure why you're calling Refresh() as it isn't necessary and will just cause extra redrawing of the entire container (guessing this is a form).

Showing WinSCP .NET assembly transfer progress on WinForm's progress bar

Have some main form on which I am calling file downloading from FTP. When this operation is raised i want to see new form as ShowDialog and progress bar on it to be shown meantime, then show the progress and close new form and back to main form. My code is working however, when it will process is started my main form freezes and after while new form is appearing and then closing. What I would like to correct is to show this new form to be showed straightaway after process is executed. Can you take a look and tell me whats wrong?
This is out of my main form the download process called:
Dim pro As New FrmProgressBarWinscp(WinScp, myremotePicturePath, ladujZdjeciaPath, True)
FrmProgressBarWinscp is as follows:
Public Class FrmProgressBarWinscp
Property _winScp As WinScpOperation
Property _remotePicture As String
Property _ladujZdjecia As String
Property _removesource As String
Public Sub New()
InitializeComponent()
End Sub
Sub New(winscp As WinScpOperation, remotePicture As String, ladujzdjecia As String, removesource As Boolean)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_winScp = winscp
_remotePicture = remotePicture
_ladujZdjecia = ladujzdjecia
_removesource = removesource
ShowDialog()
End Sub
Sub Run()
Try
Cursor = Cursors.WaitCursor
_winScp.GetFile(_remotePicture, _ladujZdjecia, _removesource)
ProgressBar1.Minimum = 0
ProgressBar1.Maximum = 1
ProgressBar1.Value = 0
Do
ProgressBar1.Value = WinScpOperation._lastProgress
ProgressBar1.Refresh()
Loop Until ProgressBar1.Value = 1
Cursor = Cursors.Default
'Close()
Catch ex As Exception
Finally
If _winScp IsNot Nothing Then
_winScp.SessionDispose()
End If
System.Threading.Thread.Sleep(10000)
Close()
End Try
End Sub
Private Sub FrmProgressBarWinscp_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Run()
End Sub
End Class
Winscp my own class and used methods:
...
Function GetFile(source As String, destination As String, Optional removeSource As Boolean = False)
Dim result As Boolean = True
Try
session.GetFiles(source, destination, removeSource).Check()
Catch ex As Exception
result = False
End Try
Return result
End Function
Private Shared Sub SessionFileTransferProgress(sender As Object, e As FileTransferProgressEventArgs)
'Print transfer progress
_lastProgress = e.FileProgress
End Sub
Public Shared _lastProgress As Integer
...
Further discussion nr 3:
Main form:
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
Return WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
End Function)
Dim forma As New FrmProgressBar
forma.ShowDialog()
Progress bar form:
Public Class FrmProgressBar
Public Sub New()
InitializeComponent()
End Sub
Sub Run()
Try
Do
ProgressBar1.Value = WinScpOperation._lastProgress
ProgressBar1.Refresh()
Loop Until ProgressBar1.Value = 1
Cursor = Cursors.Default
Catch ex As Exception
Finally
MsgBox("before sleep")
System.Threading.Thread.Sleep(10000)
MsgBox("after sleep sleep")
Close()
End Try
End Sub
Private Sub FrmProgressBarWinscp_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Run()
End Sub
End Class
Point nr. 4:
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
Return WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
End Function)
Dim pic As New Waiting
pic.ShowDialog()
Task.WaitAll(tsk)
pic.Close()
Point 5:
Dim pic As New Waiting
pic.ShowDialog()
Dim tsk As Task = Task.Factory.StartNew(Sub() WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, pic, True))
Task.WaitAll(tsk)
'pic.Close()
In some other class (maybe didn't mentioned before this method is placed in diffrent class - my custom one)
Public Function GetFile(source As String, destination As String, formclose As InvokeCloseForm, Optional removeSource As Boolean = False) As Boolean
Dim result As Boolean = True
Try
session.GetFiles(source, destination, removeSource).Check()
Catch ex As Exception
result = False
End Try
formclose.RUn()
Return result
End Function
Interface:
Public Interface InvokeCloseForm
Sub RUn()
End Interface
Waiting form :
Public Class Waiting
Implements InvokeCloseForm
Public Sub RUn() Implements InvokeCloseForm.RUn
Me.Close()
End Sub
End Class
The Session.GetFiles method in blocking.
It means it returns only after the transfer finishes.
The solution is to:
Run the WinSCP transfer (the Session.GetFiles) in a separate thread, not to block the GUI thread.
For that see WinForm Application UI Hangs during Long-Running Operation
Handle the Session.FileTransferProgress event.
Though note that the event handler will be called on the background thread, so you cannot update the progress bar directly from the handler. You have to use the Control.Invoke to make sure the progress bar is updated on the GUI thread.
For that see How do I update the GUI from another thread?
A trivial implementation is below.
For a more version of the code, see WinSCP article Displaying FTP/SFTP transfer progress on WinForms ProgressBar.
Public Class ProgressDialog1
Private Sub ProgressDialog1_Load(
sender As Object, e As EventArgs) Handles MyBase.Load
' Run download on a separate thread
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf Download))
End Sub
Private Sub Download(stateInfo As Object)
' Setup session options
Dim mySessionOptions As New SessionOptions
With mySessionOptions
...
End With
Using mySession As Session = New Session
AddHandler mySession.FileTransferProgress,
AddressOf SessionFileTransferProgress
' Connect
mySession.Open(mySessionOptions)
mySession.GetFiles(<Source>, <Destination>).Check()
End Using
' Close form (invoked on GUI thread)
Invoke(New Action(Sub() Close()))
End Sub
Private Sub SessionFileTransferProgress(
sender As Object, e As FileTransferProgressEventArgs)
' Update progress bar (on GUI thread)
ProgressBar1.Invoke(
New Action(Of Double)(AddressOf UpdateProgress), e.OverallProgress)
End Sub
Private Sub UpdateProgress(progress As Double)
ProgressBar1.Value = progress * 100
End Sub
End Class
You may want to disable the progress form (or its parts) during the operation, if you want to prevent the user from doing some operations.
Use the .Enabled property of the form or control(s).
Easier, but hacky and generally not recommendable solution, is to call the Application.DoEvents method from your existing SessionFileTransferProgress handler.
And of course, you have to update the progress bar from the the SessionFileTransferProgress as well.
Private Shared Sub SessionFileTransferProgress(
sender As Object, e As FileTransferProgressEventArgs)
'Print transfer progress
ProgressBar1.Value = e.FileProgress
Application.DoEvents
End Sub
And the progress bar's .Minimum and .Maximum must be set before the Session.GetFiles.
But do not do that! That's a wrong approach.
And still, you need to disable the forms/controls the same way as in the correct solution above.

VB Simple Threading using Delegates

I understand the concept of threading. I understand the concept of delegates but I am having trouble combining the two concepts. I followed a tutorial and I was able to make two counters start at the same time using multiple threads on my form. I was getting the cross threading error and I used the Me.CheckForIllegalCrossThreadCalls = False work around. I know my current method isnt ideal and I was wondering how I would use delegates to produce the same results. I have been at it all day and still cant seem to grasp the idea. How would I add delegates to the code below to allow two counters to work simultaneously on my form?
Public Class Form1
Dim i As Integer = 0
Dim i2 As Integer = 0
'declare two threads
'thread 1
Dim thread As System.Threading.Thread
'thread 2
Dim thread2 As System.Threading.Thread
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
'replace countup() with, this will assign the countup method to thread 1
thread = New System.Threading.Thread(AddressOf countup)
thread.Start()
End Sub
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
' countup2()
thread2 = New System.Threading.Thread(AddressOf countup2)
thread2.Start()
End Sub
Private Sub countup()
Do Until i = 100000
i = i + 1
Label1.Text = i
'We wont be able to see the label unless we refresh the form
Me.Refresh()
Loop
End Sub
Private Sub countup2()
Do Until i2 = 100000
i2 = i2 + 1
Label2.Text = i2
'We wont be able to see the label unless we refresh the form
Me.Refresh()
Loop
End Sub
End Class
I would love to see the code using delegates but what I would really like is to have the understanding of whats going on.
Thanks guys
Not sure if this is exactly what you're looking for, but here's my best shot at it:
Module Module1
Dim i As Integer = 0
Dim i2 As Integer = 0
Public Delegate Sub counting()
Sub Main()
Dim del2 As counting = AddressOf countup2
Dim callback2 As IAsyncResult = del2.BeginInvoke(Nothing, Nothing)
Dim del1 As counting = AddressOf countup
Dim callback1 As IAsyncResult = del1.BeginInvoke(Nothing, Nothing)
del2.EndInvoke(callback2)
del1.EndInvoke(callback1)
Console.ReadLine()
End Sub
Private Sub countup()
Do Until i = 100000
i = i + 1
Loop
Console.WriteLine("i = " & i)
End Sub
Private Sub countup2()
Do Until i2 = 100000
i2 = i2 + 1
Loop
Console.WriteLine("i2 = " & i2)
End Sub
End Module
Sorry I have the first and second parts reversed and it's a console app instead of a form, but I figured the important part was to demonstrate delegates...
As a note, I'm not sure how familiar you are with delegates, but I included the EndInvoke to make sure the program wouldn't terminate prior to the delegates finishing their operations. They are used to return any values or exceptions from the method call as well as making the program wait. (In this case, since it's a sub there is no return value, so I didn't bother worrying about it)
One should use Control.Invoke to execute a specified delegate on the thread that owns the control's underlying window handle. Also, replace Me.Refresh() with Thread.Sleep(1) to ensure that other threads get some execution time.
Private Sub countup()
For i As Integer = 0 To 100000
Me.Invoke(Sub() Me.Label1.Text = i.ToString())
Thread.Sleep(1)
Next
End Sub
Here's an example.
' n=0 n=1
Private threads As Thread() = New Thread(2 - 1) {Nothing, Nothing}
Private Sub ButtonsClick(sender As Object, e As EventArgs) Handles Button1.Click, Button2.Click
Dim n As Integer = -1
If (sender Is Me.Button1) Then
n = 0
ElseIf (sender Is Me.Button2) Then
n = 1
End If
If (n <> -1) Then
If (Me.threads(n) Is Nothing) Then
'Start new thread.
Me.threads(n) = New System.Threading.Thread(Sub() Me.CountUp(n))
Me.threads(n).Start()
Else
'Abort thread.
Me.threads(n).Abort()
Me.threads(n) = Nothing
End If
End If
End Sub
Public Sub DisplayCount(n As Integer, text As String)
'Inside UI thread.
If (n = 0) Then
Me.Label1.Text = text
ElseIf (n = 1) Then
Me.Label2.Text = text
End If
End Sub
Private Sub CountUp(n As Integer)
'Inside worker thread.
Try
If ((n < 0) OrElse (n > 1)) Then
Throw New IndexOutOfRangeException()
End If
For i As Integer = 0 To 100000
Me.Invoke(Sub() Me.DisplayCount(n, i.ToString()))
Thread.Sleep(1)
Next
Catch ex As ThreadAbortException
Me.Invoke(Sub() Me.DisplayCount(n, "Cancelled"))
Thread.Sleep(1)
Catch ex As Exception
'TODO: Handle other exceptions.
End Try
End Sub
using Me.CheckForIllegalCrossThreadCalls = False is not the right approach.
Basically, Cross-thread operation not valid exception is raised when a control is being updated from a thread other than the thread it was created on.
Each control exposes a InvokeRequired property that allows it to be updated in a thread-safe manner.
Therefore the right way to update the label is to use code like -
Private Delegate Sub UpdateLabelDelegate(i As Integer)
Private Sub UpdateLabel(i As Integer)
If Label1.InvokeRequired Then
Dim del As New UpdateLabelDelegate(AddressOf UpdateLbl)
Label1.Invoke(del, New Object() {i})
'Me.Refresh()
Else
' this is UI thread
End If
End Sub
Private Sub UpdateLbl(i As Integer)
Label1.Text = i.ToString()
End Sub
Delegate.BeginInvoke will execute the method on a thread pool thread. Once the method returns, the thread is returned to the pool.
So basically instead of starting a new thread, you will asynchronously execute the method using Delegate.BeginInvoke

New Multi-threading not working

I'm trying to create a thread so when I click a button it creates a new PictureBox from a class, this is how far I've got but nothing comes up on the screen at all.
Form1 code:
Public Class Form1
Private pgClass As New SecondUIClass
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
pgClass.thread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private trd As Thread
Public Sub thread()
trd = New Thread(AddressOf NewUIThread)
trd.SetApartmentState(ApartmentState.STA)
trd.IsBackground = False
trd.Start()
End Sub
Private Sub NewUIThread()
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
Application.DoEvents()
Threading.Thread.Sleep(100)
Next
counter += 1
End Sub
End Class
I have posted something similar before on here but it was different, the pictureBoxes were showing on the screen but I was trying to get them to move at the same time but they wouldn't move, they only moved one at a time. The question that I asked before was this Multi threading classes not working correctly
I made a few assumptions for this answer so it may not work for you out of the box but I think it will put you on the right track without using any Thread.Sleep calls because I personally don't like building intentional slows to my apps but that's a personal preference really.
So For my example I just used a bunch of textboxes because I didn't have any pictures handy to fiddle with. But basically to get it so that the user can still interact with the program while the moving is happening I used a background worker thread that is started by the user and once its started it moves the textboxes down the form until the user tells it to stop or it hits an arbitrary boundary that I made up. So in theory the start would be the space bar in your app and my stop would be adding another control to the collection. For your stuff you will want to lock the collection before you add anything and while you are updating the positions but that is up to your discretion.
So the meat and potatoes:
in the designer of the form I had three buttons, btnGo, btnStop and btnReset. The code below handles the click event on those buttons so you will need to create those before this will work.
Public Class Move_Test
'Flag to tell the program whether to continue or to stop the textboxes where they are at that moment.
Private blnStop As Boolean = False
'Worker to do all the calculations in the background
Private WithEvents bgWorker As System.ComponentModel.BackgroundWorker
'Controls to be moved.
Private lstTextBoxes As List(Of TextBox)
'Dictionary to hold the y positions of the textboxes.
Private dtnPositions As Dictionary(Of Integer, Integer)
Public Sub New()
' Default code. Must be present for VB.NET forms when overwriting the default constructor.
InitializeComponent()
' Here I instantiate all the pieces. The background worker to do the adjustments to the position collection, the list of textboxes to be placed and moved around the form
' and the dictionary of positions to be used by the background worker thread and UI thread to move the textboxes(because in VB.NET you can not adjust controls created on the UI thread from a background thread.
bgWorker = New System.ComponentModel.BackgroundWorker()
Me.lstTextBoxes = New List(Of TextBox)
Me.dtnPositions = New Dictionary(Of Integer, Integer)
For i As Integer = 0 To 10
Dim t As New TextBox()
t.Name = "txt" & i
t.Text = "Textbox " & i
'I used the tag to hold the ID of the textbox that coorelated to the correct position in the dictionary,
' technically you could use the same position for all of them for this example but if you want to make the things move at different speeds
' you will need to keep track of each individually and this would allow you to do it.
t.Tag = i
dtnPositions.Add(i, 10)
'Dynamically position the controls on the form, I used 9 textboxes so i spaced them evenly across the form(divide by 10 to account for the width of the 9th text box).
t.Location = New System.Drawing.Point(((Me.Size.Width / 10) * i) + 10, dtnPositions(i))
Me.lstTextBoxes.Add(t)
Next
'This just adds the controls to the form dynamically
For Each r In Me.lstTextBoxes
Me.Controls.Add(r)
Next
End Sub
Private Sub Move_Test_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
'Don't need to do anything here. Placeholder
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
Try
If Not bgWorker.IsBusy Then
'User starts the movement.
bgWorker.RunWorkerAsync()
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnReset_Click(sender As Object, e As EventArgs) Handles btnReset.Click
Try
'Reset the positions and everything else on the form for the next time through
' I don't set the blnStop value to true in here because it looked cooler to keep reseting the textboxes
' and have them jump to the top of the form and keep scrolling on their own...
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, 10)
Next
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) = 10
Next
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub bgWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Try
'This is where we do all the work.
' For this test app all its doing is scrolling through each value in the dictionary and incrementing the value
' You could make the dictionary hold a custom class and have them throttle themselves using variables on the class(or maybe travel at an angle?)
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) += 1
Next
Catch ex As Exception
blnStop = True
End Try
End Sub
Private Sub bgWorker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
Try
'Once the background worker is done updating the positions this function scrolls through the textboxes and assigns them their new positions.
' We have to do it in this event because we don't have access to the textboxes on the backgroun thread.
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, dtnPositions(CInt(r.Tag)))
Next
'use linq to find any textboxes whose position is beyond the threshhold that signifies they are down far enough.
' I chose the number 100 arbitrarily but it could really be anything.
Dim temp = From r In Me.lstTextBoxes Where r.Location.Y > (Me.Size.Height - 100)
'If we found any textboxes beyond our threshold then we set the top boolean
If temp IsNot Nothing AndAlso temp.Count > 0 Then
Me.blnStop = True
End If
'If we don't want to stop yet we fire off the background worker again and let the code go otherwise we set the stop boolean to false without firing the background worker
' so we will be all set to reset and go again if the user clicks those buttons.
If Not Me.blnStop Then
bgWorker.RunWorkerAsync()
Else
Me.blnStop = False
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
Try
'The user clicked the stop button so we set the boolean and let the bgWorker_RunWorkerCompleted handle the rest.
Me.blnStop = True
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
End Class
Theres a lot of code there but a lot of it is comments and I tried to be as clear as possible so they are probably a little long winded. But you should be able to plop that code on a new form and it would work without any changes. I had the form size quite large (1166 x 633). So I think that's when it works best but any size should work(smaller forms will just be more cluttered).
Let me know if this doesn't work for your application.
This is a problem that is well suited to async/await. Await allows you to pause your code to handle other events for a specific period of time..
Private Async Function Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) As Task Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
await pgClass.NewUIThread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private Async Function NewUIThread() As Task
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
await Task.Delay(100) 'The await state machine pauses your code here in a similar way to application.doevents() until the sleep has completed.
Next
counter += 1
End Sub
End Class

Open a modal form from a background thread to block UI thread without also blocking background thread

Maybe this is a simple question and I just don't know the correct search terms to find the answer, but my Google-fu has failed me on this one.
My vb.net application has a background thread that controls all the socket communication. Occasionally, I need this communication thread to open up a modal form to display a message and block UI interaction until the communication thread completes a series of tasks at which point, the communication thread will remove the modal form, allowing the user to continue interaction.
Currently, my communications class containing the background thread has two events, StartBlockingTask and EndBlockingTask. My main form has event listeners for these events that call like-named subs. They call code looking like this:
Private Delegate Sub BlockingDelegate(ByVal reason As String)
Private Sub StartBlockingTask(ByVal reason As String)
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
Me.Invoke(del, New Object() {reason})
Else
Try
_frmBlock.lblBlock.Text = reason
_frmBlock.ShowDialog()
Catch ex As Exception
'stuff
End Try
End If
End Sub
Private Sub EndBlockingTask()
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
Me.Invoke(del, New Object() {""})
Else
Try
If (Not _frmBlock Is Nothing) Then
_frmBlock.DialogResult = Windows.Forms.DialogResult.OK
End If
Catch ex As Exception
'stuff
End Try
End If
End Sub
This successfully blocks the UI from interaction, but it also blocks the communications thread so the EndBlockingTask event never actually gets raised. How can I open this modal dialog from the communications thread and allow the communications thread to still continue running?
Thanks in advance!
I disagree.
All that needs to be done is to change Invoke() to BeginInvoke() and you're golden.
This is because Invoke() is actually synchronous which causes it block until ShowDialog() resolves.
Using BeginInvoke() makes it asynchronous and allows the UI to be blocked while the thread continues:
Public Class Form1
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
If Not BackgroundWorker1.IsBusy Then
BackgroundWorker1.RunWorkerAsync()
End If
End Sub
Private Delegate Sub BlockingDelegate(ByVal reason As String)
Private Sub StartBlockingTask(ByVal reason As String)
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
Me.BeginInvoke(del, New Object() {reason})
Else
Try
_frmBlock.lblBlock.Text = reason
_frmBlock.ShowDialog()
Catch ex As Exception
'stuff
End Try
End If
End Sub
Private Sub EndBlockingTask()
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
Me.BeginInvoke(del, New Object() {""})
Else
Try
If (Not _frmBlock Is Nothing) Then
_frmBlock.DialogResult = Windows.Forms.DialogResult.OK
End If
Catch ex As Exception
'stuff
End Try
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For i As Integer = 1 To 10
BackgroundWorker1.ReportProgress(i)
System.Threading.Thread.Sleep(1000)
If i = 4 Then
Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
del("bada...")
ElseIf i = 7 Then
Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
del("bing!")
End If
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Label1.Text = e.ProgressPercentage
End Sub
End Class
You are calling the address from within the sub it is created in. The Address needs to be called from outside this sub.
Private Sub StartBlockingTask(ByVal reason As String)
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
Private Sub EndBlockingTask()
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
You need to create two delegates. One for StartBlockingTask and one for EndBlockingTask
This is an example from MSDN,
Delegate Sub MySubDelegate(ByVal x As Integer)
Protected Sub Test()
Dim c2 As New class2()
' Test the delegate.
c2.DelegateTest()
End Sub
Class class1
Sub Sub1(ByVal x As Integer)
MessageBox.Show("The value of x is: " & CStr(x))
End Sub
End Class
Class class2
Sub DelegateTest()
Dim c1 As Class1
Dim msd As MySubDelegate
c1 = New Class1()
' Create an instance of the delegate.
msd = AddressOf c1.Sub1
msd.Invoke(10) ' Call the method.
End Sub
End Class
http://msdn.microsoft.com/en-us/library/5t38cb9x(v=vs.71).aspx
Let me know if this helps.