I have a some code in BackgroundWorker which helps me to prevent freezing of interface while Bass connecting to audiostream:
Private Sub WorkerConnectToStream_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles WorkerConnectToStream.DoWork
Bass.BASS_StreamFree(StreamNumber)
StreamNumber = Bass.BASS_StreamCreateURL(StreamAddr, 0, BASSFlag.BASS_STREAM_STATUS, _downloadProc_, Me.Handle)
If StreamNumber <> 0 Then
Bass.BASS_ChannelPlay(StreamNumber, True)
End If
End Sub
But I get: InvalidOperationException with the message, "Control control name accessed from a thread other than the thread it was created on." If I set CheckForIllegalCrossThreadCalls property to false or I launch my program from Debug folder then all is OK but it's not best solution.
if I Use:
invoke(sub()
StreamNumber = Bass.BASS_StreamCreateURL(StreamAddr, 0, BASSFlag.BASS_STREAM_STATUS, _downloadProc_, Me.Handle)
end sub)
then this message is disappears, but interface of my form is freezes.
How I can resolve this problem?
Many thanks!
I'm Sorry for my bad english.
The problem is that your DoWork runs on background thread but calls a control on main thread. Controls can't do that. You need to raise event such as ReportProgress within your Dowork, pass data and update your control there. This is what BGW is about. It lets you interact with controls without writing tons of code to manage threads.
If you use some control for something and this control is on the form, instead, try to instantiate it on BG thread and use it in-memory only. I remember, I did something like that and it worked.
Related
I am using vb.net, and in my program I get this 'crossthread operation not valid' error when I run my backgroundworker that will make this textbox enabled true. My main sub will first turn the enabled to false, and when the backgroundworker runs it will turn it back true then exit. Why does it give me an error? FYI: There is more code to this but I don't want to make it any more confusing...
Here is the stack trace:
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.OnEnabledChanged(EventArgs e)
at System.Windows.Forms.Control.set_Enabled(Boolean value)
at Helium.Form1.BackgroundWorker1_DoWork(Object sender, DoWorkEventArgs e) in C:\Users\Kevin\documents\visual studio 2010\Projects\Helium\Helium\Form1.vb:line 167
at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)
and here is the exact error message:
{"Cross-thread operation not valid: Control 'mainText' accessed from a thread other than the thread it was created on."}
Can someone please help me out!
Thanks,
KEvin
The purpose of the BackgroundWorker class is to perform work on a non-GUI thread while the GUI remains responsive. Unless you set Control.CheckForIllegalCrossThreadCalls to false (which you shouldn't do), or use Invoke as suggested in the other answers (which I also wouldn't recommend), you're going to get an illegal cross-thread operation exception.
If you want GUI-related "stuff" to happen while your BackgroundWorker is running, I'd generally recommend using the BackgroundWorker.ReportProgress method and attaching an appropriate handler to the BackgroundWorker.ProgressChanged event. If you want something on the GUI to happen once the BackgroundWorker is finished, then simply attach your handler to update the GUI to the BackgroundWorker.RunWorkerCompleted event.
Type the following code in the Form1_Load (or whatever your form is) sub:
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
It fixes all problems with blocked cross-thread operations.
Better way to this in VB.NET is to use a Extension it makes very nice looking code for cross-threading GUI Control Calls.
Just add this line of code to any Module you have.
<System.Runtime.CompilerServices.Extension()> _
Public Sub Invoke(ByVal control As Control, ByVal action As Action)
If control.InvokeRequired Then
control.Invoke(New MethodInvoker(Sub() action()), Nothing)
Else
action.Invoke()
End If
End Sub
Now you can write Cross-Thread Control code that's only 1 line long for any control call.
Like this, lets say you want to clear a ComboBox and it's called from threads or without threads you can just use do this now
cboServerList.Invoke(Sub() cboServerList.Items.Clear())
Want to add something after you clear it?
cboServerList.Invoke(Sub() cboServerList.Items.Add("Hello World"))
Where exactly do you set the Enabled property? If you do it within the DoWork event handler, this code is running on a different thread than the button was created on, which should give the exception that you experience. To get around this, you should use BeginInvoke. For convenience it could be wrapped into a method, like so:
Private Sub SetControlEnabled(ByVal ctl As Control, ByVal enabled As Boolean)
If ctl.InvokeRequired Then
ctl.BeginInvoke(New Action(Of Control, Boolean)(AddressOf SetControlEnabled), ctl, enabled)
Else
ctl.Enabled = enabled
End If
End Sub
Now you can safely call that method to enable or disable any control from any thread:
SetControlEnabled(someButton, False)
You cannot directly set a control's property that is on the UI thread from another thread. It can be done though, here is an example from msdn.
Private Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the'
' calling thread to the thread ID of the creating thread.'
' If these threads are different, it returns true.'
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
Your Form_Load () pls write below code part. All your problems will be solved.
'## crossed-thread parts will not be controlled by this option...
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
Suggest to use AutomationPeer.
For example below code calls Execute button when I press F5 key. Similarly if you want a thread or Background worker to call an event or function you can use Automation Peer. In your case instead of button (Which I used here) you can use text box with its appropriate property to invoke.
'Button name=btnExecute
'Imports System.Windows.Automation.Peers
'Imports System.Windows.Automation.Provider
If e.Key = Key.F5 Then
Dim peer As New ButtonAutomationPeer(btnExecute)
Dim invokeProv As IInvokeProvider = TryCast(peer.GetPattern(PatternInterface.Invoke), IInvokeProvider)
invokeProv.Invoke()
End If
Regards
RV
CheckForIllegalCrossThreadCalls = False
I have a main form wich is expected to perfom some long operations. In parallel, I'm trying to display the percentage of the executed actions.
So I created a second form like this:
Private Delegate Sub DoubleFunction(ByVal D as Double)
Private Delegate Sub EmptyFunction()
Public Class LoaderClass
Inherits Form
'Some properties here
Public Sub DisplayPercentage(Value as Double)
If Me.InvokeRequired then
dim TempFunction as New DoubleFunction(addressof DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End if
End sub
Public Sub CloseForm()
If Me.InvokeRequired Then
Dim CloseFunction As New EmptyFunction(AddressOf CloseForm)
Me.Invoke(CloseFunction)
Else
Me.Close()
End If
FormClosed = True
End Sub
End class
My main sub, the one which is expected to perform the long operations is in another form as follows:
Private Sub InitApplication
Dim Loader as new LoaderClass
Dim LoaderThread as new thread(Sub()
Loader.ShowDialog()
End sub)
LoaderThread.start()
Loader.DisplayPercentage(1/10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2/10)
ConnectToDataBase()
Loader.DisplayPercentage(3/10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4/10)
LoadClients()
...
Loader.CloseForm()
End sub
The code works almost 95% of the time but sometimes I'm getting a thread exception somewhere in the sub DisplayPercentage. I change absolutely nothing, I just hit the start button again and the debugger continues the execution without any problem.
The exception I get is: Cross-thread operation not valid: Control 'LoaderClass' accessed from a thread other than the thread it was created on event though I'm using : if InvokeRequired
Does anyone know what is wrong with that code please ?
Thank you.
This is a standard threading bug, called a "race condition". The fundamental problem with your code is that the InvokeRequired property can only be accurate after the native window for the dialog is created. The problem is that you don't wait for that. The thread you started needs time to create the dialog. It blows up when InvokeRequired still returns false but a fraction of a second later the window is created and Invoke() now objects loudly against being called on a worker thread.
This requires interlocking, you must use an AutoResetEvent. Call its Set() method in the Load event handler for the dialog. Call its WaitOne() method in InitApplication().
This is not the only problem with this code. Your dialog also doesn't have a Z-order relationship with the rest of the windows in your app. Non-zero odds that it will show behind another window.
And an especially nasty kind of problem caused by the SystemEvents class. Which needs to fire events on the UI thread. It doesn't know what thread is the UI thread, it guesses that the first one that subscribes an event is that UI thread. That turns out very poorly if that's your dialog when it uses, say, a ProgressBar. Which uses SystemEvents to know when to repaint itself. Your program will crash and burn long after the dialog is closed when one of the SystemEvents now is raised on the wrong thread.
Scared you enough? Don't do it. Only display UI on the UI thread, only execute slow non-UI code on worker threads.
Thank you for your proposal. How to do that please ? Where should I
add Invoke ?
Assuming you've opted to leave the "loading" code of the main form in the main UI thread (probably called from the Load() event), AND you've set LoaderClass() as the "Splash screen" in Project --> Properties...
Here is what LoaderClass() would look like:
Public Class LoaderClass
Private Delegate Sub DoubleFunction(ByVal D As Double)
Public Sub DisplayPercentage(Value As Double)
If Me.InvokeRequired Then
Dim TempFunction As New DoubleFunction(AddressOf DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End If
End Sub
End Class
*This is the same as what you had but I moved the delegate into the class.
*Note that you do NOT need the CloseForm() method as the framework will automatically close your splash screen once the main form is completely loaded.
Now, over in the main form, you can grab the displayed instance of the splash screen with My.Application.SplashScreen and cast it back to LoaderClass(). Then simply call your DisplayPercentage() method at the appropriate times with appropriate values:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitApplication()
End Sub
Private Sub InitApplication()
Dim Loader As LoaderClass = DirectCast(My.Application.SplashScreen, LoaderClass)
Loader.DisplayPercentage(1 / 10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2 / 10)
ConnectToDataBase()
Loader.DisplayPercentage(3 / 10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4 / 10)
LoadClients()
' Loader.CloseForm() <-- This is no longer needed..."Loader" will be closed automatically!
End Sub
Private Sub LoadLocalConfiguration()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub ConnectToDataBase()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadInterfaceObjects()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadClients()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
End Class
If all goes well, your splash screen should automatically display, update with progress, then automatically close when your main form has finished loading and displayed itself.
Me.Invoke(TempFunction, Value)
Should be:
Me.Invoke(TempFunction, new Object(){Value})
because the overload with parameters takes an array of parameters.
Value is on the stack of the function in the current thread. You need to allocate memory on the GC heap and copy the value to that memory so that it is available to the other thread even after the local stack has been destroyed.
I'm trying to make a program of mine into a multithreaded application, but I've hit a pair of snags that I documented in the following code. Any help that I can get with this to make it behave properly would be greatly appreciated so I can expand this stub into a more efficient version of my existing application.
Thank you for any advice you have on this matter.
- Aaron
Imports System.Threading
Public Class frmMain
''' <summary>Initializes the multithreaded form</summary>
Private Sub Initialize() Handles MyBase.Load
AddThread(AddressOf Update_UI)
running = True
For Each Thread In ThreadPool
Thread.IsBackground = True
Thread.Start()
Next
End Sub
''' <summary>Terminates the multithreaded form</summary>
Protected Overrides Sub Finalize() Handles MyBase.FormClosing
running = False
For Each Thread In ThreadPool
Thread.Join()
Thread = Nothing
Next
End Sub
''' <summary>Adds a worker thread to the ThreadPool</summary>
''' <param name="pointer">The AddressOf the function to run on a new thread.</param>
Private Sub AddThread(ByRef pointer As System.Threading.ParameterizedThreadStart)
Dim newthread As Integer
If ThreadPool Is Nothing Then newthread = 0 Else newthread = ThreadPool.GetUpperBound(0) + 1
ReDim Preserve ThreadPool(newthread)
ThreadPool(newthread) = New Thread(pointer)
End Sub
''' <summary>Updates the User Interface</summary>
Private Sub Update_UI()
'HELP: The commented out lines in this subroutine make the program work incorrectly when uncommented.
'HELP: It should echo 'output' to the titlebar of frmMain, but it also makes the form unresponsive.
'HELP: When I force the form to quit, the 'termination alert' does not trigger, instead the application hangs completely on Thread.Join (see above).
'HELP: If I remove DoEvents(), the form is unable to be closed...it simply goes unresponsive. Shouldn't the multithreading keep us from needing DoEvents()?
'If Me.InvokeRequired Then
' Me.Invoke(New MethodInvoker(AddressOf Update_UI))
'Else
While running
Dim output As String = System.DateTime.Now + " :: Update_UI() is active!"
Debug.Print(output)
'Application.DoEvents()
'Me.Text = output
End While
Debug.Print(System.DateTime.Now + " :: Termination signal recieved...")
'End If
End Sub
Delegate Sub dlgUpdate_UI()
Private ThreadPool() As Thread
Private running As Boolean
End Class
Yes, none of what you tried can work properly. You correctly identified the need to use Control.Invoke() to run the Me.Text assignment on the main thread. This is what is going wrong:
Your Invoke() call makes the entire method run on the main thread. It will start executing the loop and never exit. Your form goes catatonic since it can't do anything else anymore, like repaint the caption bar to show the changed text or respond to user input
The DoEvents call makes the form come back alive but now you've got a new problem: the user can close the window and your code keeps running. The running flag will never be set to false so the program won't stop. The user interface is gone though. Code would normally bomb on an ObjectDisposedException but not in your specific case, the Text property is stored in a private variable
You could alter the code so that only the Me.Text assignment runs on the main thread. But now you've got a new problem: the main thread will get pummeled by invoke request and doesn't get around to doing its regular (low priority) duties anymore. It goes catatonic. The essential problem is that you are trying to update the caption bar way too fast. There's no point, the user cannot read that fast. Update 20 times per second is plenty and looks smooth to the human eye
Do not use the Finalize() method for tasks like this, the code can easily trigger the 2 second finalizer thread time-out, bombing your program.
Do consider using the BackgroundWorker class, it takes care of some of these details.
It's the do while loop that is burning up all your cycles so you are loosing the battle and keeping the procesor busy no matter how many threads you use. Something like the following will be better suited for what you are trying to achieve.
Imports System.Threading
Public Class Form1
Private t As New Timer(AddressOf DoTimer, Nothing, 1000, 1000)
Private Sub DoTimer(ByVal state As Object)
UpdateUi()
End Sub
''' <summary>Updates the User Interface</summary>
Private Sub UpdateUi()
If InvokeRequired Then
Invoke(New DlgUpdateUi(AddressOf UpdateUi))
Else
Dim output As String = DateTime.Now & " :: Update_UI() is active!"
Debug.Print(output)
Text = output
End If
End Sub
Delegate Sub DlgUpdateUi()
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
t.Dispose()
End Sub
End Class
If I have said once I have said it a million times. Using Invoke, while useful in many situations, is abused and way overused. If all you want to do is have the progress of the worker threads displayed to a user then using Invoke is not always the best option. And it does not look like the best option here either.
Instead, publish the status text you are assigning to output into a variable that can be accessed via the UI thread. Then use a System.Windows.Forms.Timer to periodically poll its value at a more reasonable rate...maybe every 1 second or so. The Tick event already runs on the UI thread so you can immediately begin using this value to display to the end user by manipulating the various UI controls.
Strings are really easy to pass around from thread to thread because they are immutable which means they are inherently thread-safe. The only thing you really have to worry about is making sure the UI thread sees the most recent reference published to the shared variable. In C# you would use the volatile keyword for this. In VB you can use Thread.VolatileRead from the UI thread and Thread.VolatileWrite from the worker thread. Of course, if you are more comfortable wrapping the reads and writes in a SyncLock that is perfectly acceptable as well.
I am using vb.net, and in my program I get this 'crossthread operation not valid' error when I run my backgroundworker that will make this textbox enabled true. My main sub will first turn the enabled to false, and when the backgroundworker runs it will turn it back true then exit. Why does it give me an error? FYI: There is more code to this but I don't want to make it any more confusing...
Here is the stack trace:
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.OnEnabledChanged(EventArgs e)
at System.Windows.Forms.Control.set_Enabled(Boolean value)
at Helium.Form1.BackgroundWorker1_DoWork(Object sender, DoWorkEventArgs e) in C:\Users\Kevin\documents\visual studio 2010\Projects\Helium\Helium\Form1.vb:line 167
at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)
and here is the exact error message:
{"Cross-thread operation not valid: Control 'mainText' accessed from a thread other than the thread it was created on."}
Can someone please help me out!
Thanks,
KEvin
The purpose of the BackgroundWorker class is to perform work on a non-GUI thread while the GUI remains responsive. Unless you set Control.CheckForIllegalCrossThreadCalls to false (which you shouldn't do), or use Invoke as suggested in the other answers (which I also wouldn't recommend), you're going to get an illegal cross-thread operation exception.
If you want GUI-related "stuff" to happen while your BackgroundWorker is running, I'd generally recommend using the BackgroundWorker.ReportProgress method and attaching an appropriate handler to the BackgroundWorker.ProgressChanged event. If you want something on the GUI to happen once the BackgroundWorker is finished, then simply attach your handler to update the GUI to the BackgroundWorker.RunWorkerCompleted event.
Type the following code in the Form1_Load (or whatever your form is) sub:
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
It fixes all problems with blocked cross-thread operations.
Better way to this in VB.NET is to use a Extension it makes very nice looking code for cross-threading GUI Control Calls.
Just add this line of code to any Module you have.
<System.Runtime.CompilerServices.Extension()> _
Public Sub Invoke(ByVal control As Control, ByVal action As Action)
If control.InvokeRequired Then
control.Invoke(New MethodInvoker(Sub() action()), Nothing)
Else
action.Invoke()
End If
End Sub
Now you can write Cross-Thread Control code that's only 1 line long for any control call.
Like this, lets say you want to clear a ComboBox and it's called from threads or without threads you can just use do this now
cboServerList.Invoke(Sub() cboServerList.Items.Clear())
Want to add something after you clear it?
cboServerList.Invoke(Sub() cboServerList.Items.Add("Hello World"))
Where exactly do you set the Enabled property? If you do it within the DoWork event handler, this code is running on a different thread than the button was created on, which should give the exception that you experience. To get around this, you should use BeginInvoke. For convenience it could be wrapped into a method, like so:
Private Sub SetControlEnabled(ByVal ctl As Control, ByVal enabled As Boolean)
If ctl.InvokeRequired Then
ctl.BeginInvoke(New Action(Of Control, Boolean)(AddressOf SetControlEnabled), ctl, enabled)
Else
ctl.Enabled = enabled
End If
End Sub
Now you can safely call that method to enable or disable any control from any thread:
SetControlEnabled(someButton, False)
You cannot directly set a control's property that is on the UI thread from another thread. It can be done though, here is an example from msdn.
Private Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the'
' calling thread to the thread ID of the creating thread.'
' If these threads are different, it returns true.'
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
Your Form_Load () pls write below code part. All your problems will be solved.
'## crossed-thread parts will not be controlled by this option...
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
Suggest to use AutomationPeer.
For example below code calls Execute button when I press F5 key. Similarly if you want a thread or Background worker to call an event or function you can use Automation Peer. In your case instead of button (Which I used here) you can use text box with its appropriate property to invoke.
'Button name=btnExecute
'Imports System.Windows.Automation.Peers
'Imports System.Windows.Automation.Provider
If e.Key = Key.F5 Then
Dim peer As New ButtonAutomationPeer(btnExecute)
Dim invokeProv As IInvokeProvider = TryCast(peer.GetPattern(PatternInterface.Invoke), IInvokeProvider)
invokeProv.Invoke()
End If
Regards
RV
CheckForIllegalCrossThreadCalls = False
I need to show a screen or something, saying 'Loading' or whatever while long process are working.
I am creating a application with the Windows Media Encoder SDK and it takes awhile to initialize the encoder. I would like for a screen to pop up saying 'Loading' while it is starting the encoder, and then for it to disappear when the encoder is done and they can continue with the application.
Any help would be appreciated. Thanks!
Create a Form that will serve as the "Loading" dialog. When you're ready to initialize the encoder, display this form using the ShowDialog() method. This causes it to stop the user from interacting with the form that is showing the loading dialog.
The loading dialog should be coded in such a way that when it loads, it uses a BackgroundWorker to initialize the encoder on a separate thread. This ensures the loading dialog will remain responsive. Here's an example of what the dialog form might look like:
Imports System.ComponentModel
Public Class LoadingForm ' Inherits Form from the designer.vb file
Private _worker As BackgroundWorker
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
_worker = New BackgroundWorker()
AddHandler _worker.DoWork, AddressOf WorkerDoWork
AddHandler _worker.RunWorkerCompleted, AddressOf WorkerCompleted
_worker.RunWorkerAsync()
End Sub
' This is executed on a worker thread and will not make the dialog unresponsive. If you want
' to interact with the dialog (like changing a progress bar or label), you need to use the
' worker's ReportProgress() method (see documentation for details)
Private Sub WorkerDoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
' Initialize encoder here
End Sub
' This is executed on the UI thread after the work is complete. It's a good place to either
' close the dialog or indicate that the initialization is complete. It's safe to work with
' controls from this event.
Private Sub WorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
Me.DialogResult = Windows.Forms.DialogResult.OK
Me.Close()
End Sub
End Class
And, when you're ready to display the dialog, you would do so like this:
Dim frm As New LoadingForm()
frm.ShowDialog()
There are more elegant implementations and better practices to follow, but this is the simplest.
There are many ways you could do this. The most simple might be to show a modal dialog, then kick off the other process, once it is completed you an then close the displayed dialog. You will need to handle the display of the standard X to close though. However, doing that all in the standard UI thread would lock the UI until the operation is complete.
Another option might be to have a "loading" screen that fills your default form, bring it to the front, then trigger the long running process on a secondary thread, once it is completed you can notify the UI thread and remove the loading screen.
Those are just a few ideas, and it really depends on what you are doing.
Two things you can try.
After setting your label (as mentioned in the comment to Mitchel) call Application.DoEvents()
Another option you have is to run the initialization code for the encoder in a BackgroundWorker process.