Johngeh

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.


Re: Visual Basic General Cross-thread operation not valid

David M. Kean

Well you really should care what thread is accessing a control. You are not using VB6 now.

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.





Re: Visual Basic General Cross-thread operation not valid

Joe_MS

Hi,

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





Re: Visual Basic General Cross-thread operation not valid

DanKirkwoodJr

I'm lookin around a bit and and I think I'm just not gettin' it.  I see this reporting progress methods that get handled by events that perhaps run on seperate thread as the thread that issued the progresschange/report method and theres a finish event that probably happens automatically... But if this is a "background" worker, then should other events in the main UI still happen and the rest of the app continue running like nothing's goin on, because I havent acheived that in a clean manner so far, as far as I know.  I expected that the dowork event would run async, and the main app would continue like the dowork isnt processing, and if main app was gonna try to issue the worker to runansync again for another job, then it should probably check the busy state first... instead, the dowork seems to happen and lock everything down until it finishes, and unless I throw a doevents() method in there, the rest of the app's events seem frozen waiting for the dowork event to finish... I figured the whole point was to avoid DoEvents()... the reporting and event complete stuff seems more trivial to me, nice but not the "heart" of the matter... like I said, maybe Im just not gettin it... any help with a reply would be appreciated... thx



Re: Visual Basic General Cross-thread operation not valid

DanKirkwoodJr

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





Re: Visual Basic General Cross-thread operation not valid

DanKirkwoodJr

OK - this will probably be my last post for whomever might read this, no matter what else I might figure out... After some cross referencing the team system/framework 2 coding with a new sample I started in 2003/1.1 with thread object... I see that thread when started DID run async immediately and easily, using a synclock concept on a queue var for the state, and actually in the 2003 example on a listbox I was filling too (which may be good stuff put in 2.0 that objects are more protected at compile time, when they're bein referenced and shouldnt be where I earlier could not synclock a listbox from team sys/2.0 - anyways,) I was able to achieve after not too long threadsafe code, the state queue var was my way of knowing when end of thread happened, maybe theres better techniques... in my 2.0 example I, at thread-start after recreating the thread using the new thread(addressof dowork) init call to the thread, synclocked the state, cleared it, and enqueued an integer 1... then at thread worker-rtn-end, I synclocked the state queue and enqueued a 2... then in a timer event always goin in main UI, I synclocked the state queue, checked the count was > 0 and if so, peeked the end to see if was 2, if so, I made changes to the UI that were desired knowing now the thread must have finished...  the 1.1 code was simpler, I synlocked the lstbox and never worried about when ended... however the thread logic in both the 1.1 and 2.0 examples were about the same, but ran async for 1.1 environment, not for 2.0 (beta) Ive got... so maybe is a bug that will be fixed by 2.0 production release... or theres somethin new changed or  introduced in 2.0 I'm unfamiliar with, but I havent had successful multithreading in 2.0 without doevents() usage so far... the backgroundworker component seems the easiest way to implement all of these ideas... but again didnt run async for me, and that problem may be related to the 2.0 thread example I mentioned above... Happy coding!



Re: Visual Basic General Cross-thread operation not valid

TaDa

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





Re: Visual Basic General Cross-thread operation not valid

TaDa

>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<

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...




Re: Visual Basic General Cross-thread operation not valid

DanKirkwoodJr

Thx for the reply... you did help me get on the right track... and helped me get more familiar with the delagate/invoke technique... I liked that...

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




Re: Visual Basic General Cross-thread operation not valid

DanKirkwoodJr

I wondered about that too, I considered maybe some magic was happening there when using the background component, perhaps it is needed there, but atleast the component is givin us events easy to tap into things, but then invoke/or careful synclock's need to be applied perhaps...

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. ...




Re: Visual Basic General Cross-thread operation not valid

Carlo Razzeto

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.





Re: Visual Basic General Cross-thread operation not valid

Bruce Stone

I guess I'm missing something as well... If you have the time I'll pose this problem.

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






Re: Visual Basic General Cross-thread operation not valid

MS Tobin Titus

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





Re: Visual Basic General Cross-thread operation not valid

vaishali.mspp

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






Re: Visual Basic General Cross-thread operation not valid

blomm

or, you could just set

ystem.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false

and all your cross threading problems go away.