Please tell me that you are going to fix this before the official release!!
I am using VB not C++. I really shouldn't have to care that my code is not running in the same thread as the one that created the form that contains the control that I am trying to update.
Please fix this or at least have some real examples that show how to update a control on a form. The examples that I found were not helpful at all.

Cross-thread operation not valid
hemo
Since you haven't posted any code, it's impossible to tell you where the problems lie, but see if this helps
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim wk As New Worker("The String")
Dim WkThread As New System.Threading.Thread(AddressOf wk.DoSomeWork)
AddHandler wk.DataChanged, AddressOf UpdateUI
WkThread.Start()
End Sub
Private Delegate Sub UpdateUIDelegate(ByVal data As String)
Private Sub UpdateUI(ByVal sender As Object, ByVal args As Worker.WorkerArgs)
If Me.InvokeRequired Then
Dim Parms() As Object = {args.Data}
Me.Invoke(New UpdateUIDelegate(AddressOf ChangeLabel), Parms)
Else
ChangeLabel(args.Data)
End If
End Sub
Private Sub ChangeLabel(ByVal s As String)
Label1.Text = s
End Sub
Worker Class:
Public Class Worker
Private Data As String
Public Event DataChanged(ByVal sender As Object, ByVal Args As WorkerArgs)
Public Sub New(ByVal SomeData As String)
Data = SomeData
End Sub
Public Sub DoSomeWork()
'simulate some work
System.Threading.Thread.CurrentThread.Sleep(5000)
Data &= " with changes made!!"
RaiseEvent DataChanged(Me, New WorkerArgs(Data))
End Sub
Public Class WorkerArgs
Private TheData As String
Public Sub New(ByVal Data As String)
TheData = Data
End Sub
Public ReadOnly Property Data() As String
Get
Return TheData
End Get
End Property
End Class
End Class
ycjj
Bruce,
Hey man, thanks a million for being a trooper and trying your best to work your way through this. I know that working through threading issues is a hard concept to grasp -- particularly if you come from a vb6 background where threads just weren't an issue -- but also not an option. That's right, in VB6, you didn't have the option to run code asynchronously. Even calling the Windows API to create a thread wouldn't produce a truely asynchronous execution from VB6. So, giving Threading to VB users was a challenge -- not only because of the complexity involved in providing the feature, but also in providing support to those who aren't used to dealing with the additional need.
So here goes. First off, while your attempt to use the background worker process is a valiant one, it unfortunately isn't what you want to use in your instance. You see, the backgroundworker component is meant to kick off asynchronous processes and then update the UI from events received from the thread. Since your socket code is already running on another thread (know it or not), there is no need to set up the background worker every time you want to update the UI. In fact, your work is nearly finished!
Here's what I recommend. First, create a delegate on your form called UpdateTextHandler. If you aren't used to delegates, don't panic! I'll get you through this. To add the delegate, add the following code under your Form declaration:
Delegate Sub UpdateTextHandler( ByVal message as String )
Second, add a method to your form called UpdateText as follows:
Public Sub UpdateText( ByVal message as String )
Me.LSOS.Text += message
End Sub
Lastly, update the your MTAConnection_DataReceived method. Change any calls to update your LSOE textbox as follows:
Case "LSOE" 'SMTP Out Queue End
Me.Invoke( New UpdateTextHandler( _
AddressOf UpdateText ), new Object() _
{ StringData.Substring( Pos + 1, _
StringData.Length() - (Pos + 3)) } )
You can do updates to different textboxes a million ways but let's move on for now. Does this seems complicated OK. Let me explain this madness. For ease of explanation, think of a delegate as a placeholder for a method. A delegate allows you to connect to any method that matches the "signature" the delegate has. So, the UpdateTextHandler and UpdateLsoeText has the same signature. I can create a new UpdateTextHandler delegate instance by passing in the addressof the method I want to execute. Since the method has parameters, I have to pass those in as well. That's why I have the object array after the handler I can use the object array to pass in the value to the delegate as though I was passing it to the method itself.
So how do you update different text boxes Here goes. Take ALL of the code from your DataReceived event handler, and put it into your UpdateText method. Inside your DataReceived event handler add the following code:
Me.Invoke( New UpdateTextHandler( _
AddressOf UpdateText ), _
New Object() { StringData } )
That should be it! If you still don't understand what is going on, drop me a line and I'll be glad to help.
HTH,
Tobin
killerless
Been experimentin more with it, and with the advent of the doevents() in the dowork event, I do seem to get what I want and the completed event is nice... But I started to get the idea that perhaps this object was never meant for what I was trying to do and began to look at other threading code out there... I still was havin a prob, starting a thread (thread object) and gettin it to run like I wanted, here I was coding for framework 2 and my guess is currentthread.sleep method disappeared which may have been what I wanted... so I was not gettin async processing with that object either... I ran across an asynccallback object with a begininvoke method - I was a little lost with it, created a delagate, issued a begininvoke() method and I'd get a routine to run but not asnyc either... I am thinking something has to have changed, because I was sure I did this once a long time ago with the thread object and the start() method, but perhaps then I used a combination of sleep() and doevents() methods to get the job done then too... maybe what Ive been tryin to do is bad practice... I do know in old vb6 programs, the advent of just one single doevents() could mess alot of things up... and since with vb.net, I've tried to code threadsafe but have had to resort to alot of synclock blocks, seems still to be a delicate practice and one that requires alot of careful consideration... any opinions on this are glady accepted, thx again
Simone1
There are hundreds of examples on how to do this correctly.
Some including:
http://msdn.microsoft.com/library/default.asp url=/library/en-us/dnforms/html/winforms06112002.asp
http://msdn.microsoft.com/library/default.asp url=/library/en-us/dnforms/html/winforms08162002.asp frame=true
http://msdn.microsoft.com/library/default.asp url=/library/en-us/dnforms/html/winforms01232003.asp frame=true
Or have a look at the new BackgroundWorker component.
Sianspheric
ystem.Windows.Forms.
Control.CheckForIllegalCrossThreadCalls = falseand all your cross threading problems go away.
John05
I first dropped the code in 2003, created a project with your code, ran like a charm...
I then dropped the code in to a 2005 sample project, it worked good there too... So I was wondering what was different except for the invoke part... I wasnt usin a class for my threadwork... So I moved the code into the same main line code and out of the seperate class (Worker)... modified the surrounding code a lil... and it worked that way too...
I then considered maybe the fact that the simulated work was only doing a sleep, I considered that was giving back cpu, and then doing a quick command later to change the string and all, so I made it add a string to a queue 450000 times in a loop instead of sleep... then I noticed that just that alone would cause some lil pauses in the responsiveness by the main app BUT it would still work, running in the background...
I discovered soon later this morning after testing that code, figuring theres gotta be somethin else goin on in my code, and sure enough there was, didnt debug it long/good enough... I kept thinking the problem had to be in the thread runnin along with the main line timer tick event goin off every 100 ms...
The timer tick event was the culprit. The 2.0 thread was runnin' so fast that it would fill the queue way up or finish entirely within the 100ms... the timer event would see 'em and try to process all of what was in the queue at the start of the tick event, assuming the numbers were smaller and the queue was still growing, it would capture the total size even if the queue had 90000 items in it, start a loop for 1 to 90000 and then try to add each to the main UI listbox 1 at a time, the rest of the app would freeze up during this processing - not the thread's processing...
So just to verify the concepts, I changed the timer event to run every 10ms instead and only do 1 add at a time to the listbox... Then all was well... But the listbox takes alot longer to fill that way... but... atleast my threads were good. In a real-world need like this, perhaps theres an addrange method for the listbox that could be utilized but thats another coding issue...
I'm kinda new to this multithreadin... but I think Im startin' to get it down...
Thx again for the reply
SterlingH
I have noticed a couple of other differences though... in 2005/2.0, I could not (if my memory from yesterday serves me correctly,) synclock a boolean,int (something about reference types for those,) or a listbox saying somethin about wrong type or object was created from different thread or somethin... I only tried those mentioned and a queue variable, the queue variable was working for me... in 2003/1.1; however, a listbox I was able to synclock, and then thread code or base main-line code could access it... Im not sure why these things are this way...
And lastly, another interesting thing I mentioned somewhere above, the sleep method dissappeared... well, its not in the code completion, but when I used the above last code example, it worked with a IDE warning...
System.Threading.Thread.CurrentThread.Sleep(5000)
Warning 1 Access of shared member or nested type through an instance; qualifying expression will not be evaluated. ...
hawash
Me.ListBox1.Items.Add(e.UserState)
End Sub<
Are you sure you don't need to use Invoke here In 1.1 the event handler still runs on the thread that raised the event...
briggins5
I'm kind of new to Windows UI programming, so maybe there's a good reason for this that I'm not aware of this... However.
Is there a reason why allowing any thread to communicate to the UI, forcing the programmer to use critical sections (mutexes) to do this properly is a bad idea While the delagate interface is great, it just seems like quite a bit of extra work compared to simply using a critical section to solve the race condition.
Vani M
I'm developing a wrapper library which provides socket data, in Byte() and Text form,
which is returned via events raised by my class module. This library is used as part of
our (US Dept of Commerce) SMTP to ZipFile Message Transfer Agent.
This event handler is used to capture socket data which is then displayed on a Windows Form to provide monitoing of the MTA processes.
When using Visual Studio 2003 this approach worked fine. The object (form) which received the event data could simply append the socket text, from the event, to a windows forms textboxes with no problems. The code is sown below:
I've left the exception handleing out.. since its not required to see what is being done.
Private Sub MTAConnection_DataReceived(ByVal StringData As String) Handles MTAConnection.DataReceived
Dim Pos As Integer
Dim MSGHeader As String
Pos = StringData.IndexOf(vbCrLf)
If Pos > 0 Then
'not an empty line
LastDataReceivedAt = Now.UtcNow
If SERV.Text <> "Service Responding" Then
SERV.Text = "Service Responding"
End If
Pos = StringData.IndexOf(":")
Try
MSGHeader = StringData.Substring(0, Pos)
Select Case MSGHeader
Case "LSOS" 'SMTP Out Queue Start
LSOS.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LSOE" 'SMTP Out Queue End
LSOE.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LSON" 'SMTP MSG Out MSGID
LSON.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LSMT" 'SMTP MSG Out Time
LSMT.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LSMI" 'SMTP MSG In MSGID
LSMI.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LSIT" 'SMTP MSG In Time
LSIT.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LRST" 'Router Queue Started
LRST.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LRET" 'Router Queue Ended
LRET.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LMRT" 'Message Routed Time
LMRT.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LMRO" ' Message Routed MSGID
LMRO.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LZPS" 'Zip Queue Start
LZPS.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LZPE" 'Zip Queue End
LZPE.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LZPN" 'Last MSG Zip
LZPN.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LZPT" 'Last Zip Time
LZPT.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LUNS" 'UnZip Queue Start
LUNS.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LUNE" 'UnZip Queue End
LUNE.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LUNN" 'Last Unzip MSGID
LUNN.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "LUNT" 'Last UnZip TIme
LUNT.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
Case "DIAG"
Dim DiagString As String
DiagString = StringData.Substring(0, StringData.Length - 2)
DiagString = DiagString.Substring(4, DiagString.Length - 4)
If DiagnosticBox.Text.Length > 20000 Then
DiagnosticBox.Text = ""
End If
DiagnosticBox.Text += vbCrLf + DiagString
Case "SERV"
SERV.Text = StringData.Substring(Pos + 1, StringData.Length() - (Pos + 3))
If SERV.Text = "Service Running" Then
SERV.ForeColor = Color.Chartreuse
Else
SERV.ForeColor = Color.Red
End If
End Select
Catch ex As Exception
End Try
End If
End Sub
I'm just starting with Visual Studio 2005 and have had no other problems geting the code to work.
The events work fine and provide the data and text which can be written to the output window with debug.writeline( ). However, when I try to set the text on the windows form textbox, an exception is thrown with the following message:
InvalidOperationException: Cross-thread operation not valid: Control 'TextBox1' accessed from a thread other than the thread it was created on.
Following your example from your post I added the code below to the Windows Form class along with a backgroundworker from the controlbox. I don't understand why the backgroundworker is unable to update the textbox. I am assuming that the task of the backgroundworker is to accept data from a seperate thread (eventsource) and provide
a means of updating the UI with the data returned by the events. It looks there is a major
departure in the way that textboxes and other wondows controls will accept data.
I'll consult the documentation for 2005 and MSDN knowedge base for an answer.
But if you have the time any assistance would be greatly appreciated.
The Exception thown with the backgroundworker is shown here.
Backgroundworker1 System.InvalidOperationException: Cross-thread operation not valid: Control 'TextBox1' accessed from a thread other than the thread it was created on.
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.set_WindowText(String value)
at System.Windows.Forms.TextBoxBase.set_WindowText(String value)
at System.Windows.Forms.Control.set_Text(String value)
at System.Windows.Forms.TextBoxBase.set_Text(String value)
at System.Windows.Forms.TextBox.set_Text(String value)
at SocketClientTest.Form1.BackgroundWorker1_ProgressChanged(Object sender, ProgressChangedEventArgs e) in G:\Visual Studio Projects\Visual Basic\SocketClient\SocketClientTest\SocketClientTest\Form1.vb:line 88
'This is the event handler that accepts the socket data
Private Sub SSLClient_ClearTextReceived(ByVal ClearTextIn As String) Handles SSLClient.ClearTextReceived
Debug.WriteLine("TextIn :" + ClearTextIn)
Newtext += ClearTextIn
If Me.BackgroundWorker1.IsBusy = True Then
Exit Sub
Else
With Me.BackgroundWorker1
.WorkerReportsProgress = True
.RunWorkerAsync(Newtext)
End With
End If
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
BackgroundWorker1.ReportProgress(100, e.Argument)
Newtext = ""
End Sub
'This is where I am trying to set the textbox text.
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Try
Debug.WriteLine("UserState " + e.UserState) 'Text is OK Here in Outout Window
Dim TextString As String = e.UserState.ToString()
TextBox1.Text += TextString 'This is where exception occurs
Catch ex As Exception
Debug.WriteLine( ex.ToString())
End Try
End Sub
Thanks much,
Bruce Stone
Jason N
Dave21
hohsen
Hi,
I’m facing some problems related to data binding. I have added a binding to a textbox.
(txtValue.DataBindings.Add ("Text", variable, "Value") ;)
The variable object implements INotifyPropertyChanged. The variable value is changed in the background by a timer (later this is a real engine value). The variable change fires a PropertyChanged event.
This event causes an exception “Cross-thread operation not valid: Control 'txtValue' accessed from a thread other than the thread it was created on.”
How can I invoke the event on the thread the control was created
I tried to troubleshoot using the following MSDN link,
http://msdn2.microsoft.com/en-us/library/ms171728(VS.80).aspx
but it doen’t not solve my problem.
More Details:
I have bound the value property of a variable object using data binding as follows
txtValue.DataBindings.Add("Text", variable, "Value");
The PropertyChanged event is raised when the value in the engine has changed.
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
_hasModifications = true;
if (this.PropertyChanged != null)
{
foreach (PropertyChangedEventHandler del in this.PropertyChanged.GetInvocationList())
{
try
{
del.Invoke(sender, args);
}
catch
{
this.PropertyChanged -= del;
}
}
}
}
How can I invoke the delegate on the txtValue control In detail how can I get the control reference when I only have the delegate
Thanks in advance,
Vaishu
PrashG
Unfortunately, enabling thread synchronization for controls is a very hard problem to solve--and even the most elegant solutions have considerable drawbacks. So the bad news is that this behavior is here to stay; the good news is that there is a new control in Whidbey that can be very helpful in these scenarios--the BackgroundWorker.
If you're not familiar with it, here's a quick walkthrough to aquaint you...
Create a new Windows Forms Application
Add a ListBox to the Form
Add a Button to the Form
Add a BackgroundWorker to the Form (it's in the Toolbox under "Components")
Double-click Button1 and add the following code to Form1:
''' <summary>
''' Tell the worker to fire progress changed events, then start it
''' </summary>
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click With Me.BackgroundWorker1
.WorkerReportsProgress = True
.RunWorkerAsync("C:\temp\bigfile.txt")
End With
End Sub ''' <summary>
''' This method is where we do the background work. I'm just reading a file as an example.
''' Each time a line is read, we fire a ProgressChanged event and pass the line that was
''' read as an argument.
'''
''' Note that we can't update the UI in the DoWork method, but we can fire events ''' and update the UI in the handler(s) for that event.
''' </summary>
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork Dim filename As String = e.Argument Using reader As New System.IO.StreamReader(filename)
While Not reader.EndOfStream
Me.BackgroundWorker1.ReportProgress(0.0, reader.ReadLine())
End While
End Using
End Sub ''' <summary>
''' You can change UI in the ProgressChanged event
''' </summary>
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.ListBox1.Items.Add(e.UserState)
End Sub ''' <summary>
''' You can also update UI in the RunWorkCompletedHandler
''' </summary>
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
MsgBox("Done reading the file!")
End Sub
If you run this code with and pass a large text file you'll see the UI update incrementally (while remaining responsive).
I hope this helps!
Joe
The VB Team