13 May 2005, 6:42 AM UTC
|
Johngeh
Posts 23
|
|
|
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.
John
|
|
|
|
Report Abuse
|
|
|
|
13 May 2005, 10:17 AM UTC
|
David M. Kean
Moderator
Posts 3,507
|
|
|
|
|
13 May 2005, 9:48 PM UTC
|
Joe_MS
Moderator
Posts 28
|
|
|
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
|
|
|
|
Report Abuse
|
|
|
|
21 Jun 2005, 3:11 PM UTC
|
DanKirkwoodJr
Posts 7
|
|
|
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
|
|
|
|
Report Abuse
|
|
|
|
21 Jun 2005, 4:30 PM UTC
|
DanKirkwoodJr
Posts 7
|
|
|
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
|
|
|
|
Report Abuse
|
|
|
|
21 Jun 2005, 6:18 PM UTC
|
DanKirkwoodJr
Posts 7
|
|
|
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!
|
|
|
|
Report Abuse
|
|
|
|
21 Jun 2005, 7:03 PM UTC
|
TaDa
Posts 363
|
|
|
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
|
|
|
|
Report Abuse
|
|
|
|
21 Jun 2005, 7:05 PM UTC
|
TaDa
Posts 363
|
|
|
>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...
|
|
|
|
Report Abuse
|
|
|
|
22 Jun 2005, 5:12 PM UTC
|
DanKirkwoodJr
Posts 7
|
|
|
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
|
|
|
|
Report Abuse
|
|
|
|
22 Jun 2005, 5:22 PM UTC
|
DanKirkwoodJr
Posts 7
|
|
|
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. ...
|
|
|
|
Report Abuse
|
|
|
|
09 Dec 2005, 5:38 PM UTC
|
Carlo Razzeto
Posts 1
|
|
|
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.
|
|
|
|
Report Abuse
|
|
|
|
19 Feb 2006, 11:10 PM UTC
|
Bruce Stone
Posts 1
|
|
|
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
|
|
|
|
Report Abuse
|
|
|
|
19 Apr 2006, 6:19 AM UTC
|
MS Tobin Titus
Posts 35
|
|
|
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
|
|
|
|
Report Abuse
|
|
|
|
02 May 2006, 11:16 AM UTC
|
vaishali.mspp
Posts 44
|
|
|
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
vaishu.mspp
|
|
|
|
Report Abuse
|
|
|
|
09 Jun 2006, 8:48 AM UTC
|
blomm
Posts 7
|
|
|
or, you could just set
ystem.Windows.Forms. Control.CheckForIllegalCrossThreadCalls = false
and all your cross threading problems go away.
|
|
|
|
Report Abuse
|
|
|
|
MSDN Forums » Visual Basic » Visual Basic General » Re: Cross-thread operation not valid
|
|
|
Subscribe to RSS
|
|