I created a background thread that get's data and returns it to the main thread; this works. Now I want to be able to stop the SQL request from the main thread (probably on a button click).
This is my test code for creating the thread (working):
Private Sub GetSql()
If (Me.InvokeRequired) Then
Me.Invoke(New GetSqlDelegate(AddressOf GetSql))
Else
Dim da As SqlDataAdapter = New SqlDataAdapter("select * from database", _
New SqlConnection(connectionString))
dt = New DataTable
da.Fill(dt)
Me.BeginInvoke(New BindDataToGridDelegate(AddressOf BindDataToGrid))
End If
End Sub
Private Delegate Sub BindDataToGridDelegate()
Private Sub BindDataToGrid()
DataGridView1.DataSource = dt
dt = Nothing
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
thrGetData = New Thread(AddressOf ThreadBackgroundData)
thrGetData.IsBackground = True
thrGetData.Start()
End Sub
How can I access the background thread to stop the query on demand?
Do I need to set a flag on the main thread telling the background thread to stop running then have the background thread poll the main thread at intervals?
Any help would be appreciated. I was trying to look for an example but I wasn't able to find a good one. Even pseudo code would help
Thanks.
DataAdapter.Fill method is synchronous, meaning it blocks current thread until it is completed. Basically, that means you can't add polling/checking in the background thread since everything it can do is execute Fill method (which may be time-consuming).
If your problem is stopping the operation itself (please note that no data may be returned), you should just call thread.Abort() from your main thread. For that you'd need to save thrGetData as class-level variable. More about Thread.Abort() may be found here.
Related
I used to be an xcode programmer.
There, when creating application, I tend to do most things on other threads.
Occasionally, such as when I want to access the UI thread, I would then do something at main thread.
Say I did
//Load some heavy resources on the web
doOnMainThread(sub () updateUIandStuff())
//Continue doing other things.
How would I implement doOnMainthread in VB?
There is an easy way to do so in objective-c a long time ago. How to do so in vb.net
There are two common methods to accomplish this; Delegates or Invoke a lambda. The following example will paste straight into a new WinForms project with two labels added in the designer:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim bw As New System.ComponentModel.BackgroundWorker
AddHandler bw.DoWork, AddressOf BackgroundWorker_DoWork
bw.RunWorkerAsync()
End Sub
Delegate Sub UpdateLabelDelegate(ByVal labelText As String)
Private Sub UpdateLabel(ByVal labelText As String)
Label2.Text = labelText
End Sub
Private Sub BackgroundWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs)
For i As Integer = 1 To 10000
Dim testText As String = "Loop#" & i.ToString
'You can easily check your thread's access to the UI using:
' WinForms "If InvokeRequired Then : End If"
' WPF "If Not Dispatcher.CheckAccess Then : End If"
'Using Invoke Lambda
Invoke(Sub() Label1.Text = testText)
'Using Delegate
Dim updateLbl As UpdateLabelDelegate = AddressOf UpdateLabel
Invoke(updateLbl, testText)
Next
End Sub
End Class
The first example Invoke(Sub() Label1.Text = testText) is my preferred method for when the amount of code that needs to be executed on the main thread is small, like a line or two. If that line is placed in a sub that may be called by the main UI thread or a background thread in different scenarios, then it should be wrapped in a If InvokeRequired Then conditional block. The second example is using a delegate, and this method is better if a larger number of lines of code need to be executed on the main thread. Either method will allow you to call code from a background thread that will be executed on the main UI thread.
As for WPF, the methodology is largely the same, but as Bradley Uffner pointed out in the comments, invoke will be called by Dispatcher
I'm new to Visual Basic and overall kind of new to coding in general.
Currently I work on a program which uses a filewatcher. But If I try this:
Public Class Form1
Private WithEvents fsw As IO.FileSystemWatcher
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fsw = New IO.FileSystemWatcher("PATH")
fsw.EnableRaisingEvents = True
' fsw.Filter = "*.settings"
End Sub
Private Sub GetSettingsFromFile()
Some Code
More Code
CheckBox1.Checked = True
End Sub
Private Sub fsw_Changed(sender As Object, e As FileSystemEventArgs) Handles fsw.Changed
fsw.EnableRaisingEvents = False 'this is set because the file is changed many times in rapid succesion so I need to stop the Filewatcher from going of 200x (anyone has a better idea to do this?)
Threading.Thread.Sleep(100)
GetSettingsFromFile()
fsw.EnableRaisingEvents = True 'enabling it again
End Sub
End Class
But when I do this (trying to change anyhting in the form) I get this error:
System.InvalidOperationException (WinForms.IllegalCrossThreadCall)
It wont stop the program from working, but I want to understand what is wrong here and why the debugger is throwing this at me
regards
The event is being raised on a secondary thread. Any changes to the UI must be made on the UI thread. You need to marshal a method call to the UI thread and update the UI there. Lots of information around on how to do that. Here's an example:
Private Sub UpdateCheckBox1(checked As Boolean)
If CheckBox1.InvokeRequired Then
'We are on a secondary thread so marshal a method call to the UI thread.
CheckBox1.Invoke(New Action(Of Boolean)(AddressOf UpdateCheckBox1), checked)
Else
'We are on the UI thread so update the control.
CheckBox1.Checked = checked
End If
End Sub
Now you simply call that method wherever you are and whatever thread you're on. If you're already on the UI thread then the control will just be updated. If you're on a secondary thread then the method will invoke itself a second time, this time on the UI thread, and the control will be updated in that second invocation.
I'm currently working on a small auto-update project for my company. After some research on multi-threading, I manage to built up the code below :
Thread #01 :
Private Sub startUpdate()
If InvokeRequired Then
Invoke(New FTPDelegate(AddressOf startUpdate))
Else
'some code here
End If
End Sub
Thread #02 which is joined by thread #01 :
Private Sub startProcess()
myThread = New Thread(Sub() startUpdate())
myThread.Start()
myThread.Join()
'another code goes here
Me.close
End Sub
And thread #02 is accessed when the form loads :
Private Sub SUpdater_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
myThread1 = New Thread(Sub() startProcess())
myThread1.Start()
End Sub
There are 2 things which I'm stuck with :
I can't access Me.close from thread #01. It fires an error:
Control is in another thread
The main form froze even though I called another thread.
Please help me fix this error.
Thank you very much.
Invocation is required every time you are to access UI elements. Calling Me.Close() starts to dispose all the form's elements (components, buttons, labels, textboxes, etc.), causing interaction with both the form itself, but also everything in it.
The only things you are not required to invoke for are properties that you know doesn't modify anything on the UI when get or set, and also fields (aka variables).
This, for example, would not need to be invoked:
Dim x As Integer = 3
Private Sub Thread1()
x += 8
End Sub
To fix your problem you just need to invoke the closing of the form. This can be done simply using a delegate.
Delegate Sub CloseDelegate()
Private Sub Thread1()
If Me.InvokeRequired = True Then 'Always check this property, if invocation is not required there's no meaning doing so.
Me.Invoke(New CloseDelegate(AddressOf Me.Close))
Else
Me.Close() 'If invocation is not required.
End If
End Sub
I am currently studying VB.NET and I got question about using thread to open the form.
For example, when I click open button, then tread will start and open another form to adding or changing data.
Therefore, I tried to implement this part such as
Private Sub menu_Click(sender As Object, e As EventArgs) Handles menu.Click
Dim A As System.Threading.Thread = New Threading.Thread(AddressOf Task_A)
A.Start()
End Sub
Public Sub Task_A()
frmBuild.Show()
End Sub
However, I am getting error to open the frmBuild by thread. Do I need to use other method to open form?
And, How can we kill the thread when fromBuild closes?
This is almost always a bad idea. You shouldn't try to use a separate thread to open a Form - instead, open all of your forms on the main UI thread, and move the "work" that would otherwise block onto background threads. BackgroundWorker is a common means of handling the work.
That being said, if you need to do this for some unusual reason, you need to do two other things.
First, you need to set the apartment state of that thread. You also need to use Application.Run to display the form, and that form must be created on the proper thread:
Private Sub menu_Click(sender As Object, e As EventArgs) Handles menu.Click
Dim th As System.Threading.Thread = New Threading.Thread(AddressOf Task_A)
th.SetApartmentState(ApartmentState.STA);
th.Start()
End Sub
Public Sub Task_A()
frmBuild = New YourForm() ' Must be created on this thread!
Application.Run(frmBuild)
End Sub
In order to close the Form from the other thread, you can use:
frmBuild.BeginInvoke(New Action(Sub() frmBuild.Close()))
And, How can we kill the thread when fromBuild closes ?
The thread will automatically shut down when the form is closed, if it's written as shown above.
I have a ProgressBar that uses the marquee style when a report is being generated. The reason I am doing this is because the ReportViewer control I use takes some time to generate the report thus making the form unresponsive. I generate the report using a thread so the ProgressBar can show that the program is working. However, when I start the thread the ProgressBar freezes. I have already tried the BackgroundWorker but that didn't work so I used my own threading.
The reason I use the Invoke() method is because I can't make changes to the ReportViewer control on the thread I created because it was created on the UI thread.
The method that takes the most time processing is the RefreshReport() method of the ReportViewer control which is why I'm trying to do that on its own thread instead of the UI thread.
Any help would be appreciated. Thanks.
Here is the code for my thread variable:
Private t As New Thread(New ParameterizedThreadStart(AddressOf GenerateReport))
Here is the code for the button that generates the report:
Private Sub btnGenerateReport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGenerateReport.Click
pbReports.Style = ProgressBarStyle.Marquee
If t.ThreadState = ThreadState.Unstarted Then
t.IsBackground = True
t.Start(ReportType.Roads)
ElseIf t.ThreadState = ThreadState.Stopped Then
t = Nothing
t = New Thread(New ParameterizedThreadStart(AddressOf GenerateReport))
t.IsBackground = True
t.Start(ReportType.Roads)
End If
End Sub
Here is the code that generates the report:
Public Sub GenerateReport(ByVal rt As ReportType)
If rvReport.InvokeRequired Then
Dim d As New GenerateReportCallBack(AddressOf GenerateReport)
Me.Invoke(d, New Object() {rt})
Else
rvReport.ProcessingMode = ProcessingMode.Remote
rvReport.ShowParameterPrompts = False
rvReport.ServerReport.ReportServerUrl = New Uri("My_Report_Server_URL")
rvReport.ServerReport.ReportPath = "My_Report_Path"
rvReport.BackColor = Color.White
rvReport.RefreshReport()
End If
If pbReports.InvokeRequired Then
Dim d As New StopProgressBarCallBack(AddressOf StopProgressBar)
Me.Invoke(d)
Else
StopProgressBar()
End If
End Sub
Your code is starting a new thread from the UI thread. The new thread then immediately marshals back to the UI thread using Invoke - so basically it's as if you hadn't made it multithreaded at all.
Instead of that, make the new thread do all the background processing it can and only marshal back to the UI for parts of the process that need to update the UI.
You can try using the ThreadPool to generate a new worker thread. I use the below in a WPF application to show a loading screen for anything that takes over 4 seconds or so.
You might need to change some of the syntax as I'm a C# guy...
Private Sub btnGenerateReport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGenerateReport.Click
pbReports.Style = ProgressBarStyle.Marquee
ThreadPool.QueueUserWorkItem(Function(th) Do
GenerateReport(ReportType.Roads)
Dispatcher.BeginInvoke(DispatcherPriority.Normal, DirectCast(Function() Do
StopProgressBar()
End Function, Action)
End Function)
End Sub
Also, I believe that the Dispatcher.BeginInvoke is only in WPF and not in WinForms, so you my need to change that back to Me.Invoke or something.