Newbie: Invalid cross thread access with using busyindicator (silverlight and wcf service) - wcf

I have a problem with the busyindicator control of silverlight.
I have a datagrid (datagrid1) with its source set to a wcf service (client).
I would like to use the busyindicator control (bi) of silvelright toolkit when the datagrid loads itself.
But I have an "Invalid cross thread access" when I use "ThreadPool".
Sub LoadGrid()
Dim caisse As Integer = ddl_caisse.SelectedValue
Dim env As Integer = ddl_env.SelectedValue
bi.IsBusy = True
ThreadPool.QueueUserWorkItem(Sub(state)
AddHandler client.Get_PosteSPTCompleted, AddressOf client_Get_PosteSPTCompleted
client.Get_PosteSPTAsync(caisse, env)
Dispatcher.BeginInvoke(Sub()
bi.IsBusy = False
End Sub)
End Sub)
End Sub
Private Sub client_Get_PosteSPTCompleted(sender As Object, e As ServiceReference1.Get_PosteSPTCompletedEventArgs)
DataGrid1.ItemsSource = e.Result ' Here, Invalid cross thread access
End Sub
I know that the datagrid control doesn't exist in the "new thread", but how have to I make to avoid this error?
Thank you.
William

First point: Why are you using the ThreadPool?
Using a ThreadPool would be a good idea if your service was synchronous, but your WCF service is asynchronous: it won't block your UI thread after being called using Get_PosteSPTAsync.
Second point: there seems to be an issue with your IsBusy property. You're first setting it to true, then you starts an operation asynchronously, then you set it to false immediately. You should set it to false in the Completed handler.
Third point: the client_Get_PosteSPTCompleted handler won't be executed in the same thread as the UI thread (even if you don't use the ThreadPool). That's why you'll have to use the dispatcher here so force using the UI thread.
Your DataGrid1.ItemsSource = e.Result must be modified to:
Dispatcher.BeginInvoke(Sub()
DataGrid1.ItemsSource = e.Result ' Fixes the UI thread issue
bi.IsBusy = False ' Sets busy as false AFTER completion (see point 2)
End Sub)
(note sure about the VB.Net syntax, but that's the idea)
Last point: I don't know about the client object lifetime, but you're adding a new handler to the Get_PosteSPTCompleted each time you call LoadGrid. Maybe you could think of detaching handlers after use.

Related

Threadding: Update UI without blocking the thread?

I have written a server program that does a lot of jobs in threads simultaneously.
In those threads, I have to update a ListView with status information, but as it is right now using invoke, the thread waits for the UI to finish updating the ListView.
Any good advice to how I can send the status to the ListView and continue the thread while ListView finish updating?
Here's my code...
Public Delegate Sub InfoDelegate(status As String)
Public Sub Info(status As String)
If Me.InvokeRequired Then
Dim d As New InfoDelegate(AddressOf Info)
Me.Invoke(d, status)
Else
Dim item As New ListViewItem With {
.Text = status}
With lv
.BeginUpdate()
.Items.Insert(0, item)
If .Items.Count > 500 Then
For i As Integer = Me.lv.Items.Count - 1 To 500 Step -1
Me.lv.Items.RemoveAt(i)
Next
End If
.EndUpdate()
End With
End If
End Sub
You can call Control.BeginInvoke() to invoke the method asynchronously. However that call needs to be followed by a EndInvoke() call, or else you will get memory and/or thread leaks.
In the .NET Framework versions 4.0 and up you can utilize lambda expressions to pass the IAsyncResult returned from the BeginInvoke call to the lambda expression itself. Thus, you can call EndInvoke without having it block since by the time that it is called the asynchronous operation is already finished.
Here's an example:
Dim iar As IAsyncResult = _
Me.BeginInvoke(Sub()
Info("Status here") 'Calling your Info() method.
Me.EndInvoke(iar)
End Sub)

Getting cross thread Error even Used Invoke

Getting cross thread error when executing tcViewer.TabPages.Add(t) statement.
Code is as below.
Private Function fff(t As TabPage)
tcViewer.TabPages.Add(t) 'giving cross thread error
End Function
Function WebBrowserThread()
Dim t As TabPage = New TabPage((k + 1).ToString())
t.Name = k.ToString()
tcViewer.Invoke(fff(t))
End Function
Please guide.
I think you should move the creation of the new TabPage onto the UI thread as well:
Private Function fff(k as Integer)
Dim t As TabPage = New TabPage((k + 1).ToString())
t.Name = k.ToString()
tcViewer.TabPages.Add(t)
End Function
Function WebBrowserThread()
tcViewer.Invoke(fff(k))
End Function
When you construct the TabPage, you eventually reach this call stack:
System.Windows.Forms.dll!System.Windows.Forms.Control.CreateHandle()
System.Windows.Forms.dll!System.Windows.Forms.Application.MarshalingControl.MarshalingControl()
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.MarshalingControl.get()
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.WindowsFormsSynchronizationContext()
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.InstallIfNeeded()
System.Windows.Forms.dll!System.Windows.Forms.Control.Control(bool autoInstallSyncContext)
System.Windows.Forms.dll!System.Windows.Forms.ScrollableControl.ScrollableControl()
System.Windows.Forms.dll!System.Windows.Forms.Panel.Panel()
System.Windows.Forms.dll!System.Windows.Forms.TabPage.TabPage()
System.Windows.Forms.dll!System.Windows.Forms.TabPage.TabPage(string text)
At this point, the Handle is being created, and if you're doing that on the wrong thread, everything else is going to start going wrong (because the thread that the control was created on isn't going to run a message pump)
i don't know what invoking error you get but i suggest disabling cross-thread checking by adding this in the constructor or loaded event (very helpful when dealing with APIs)
Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
Check this http://tech.xster.net/tips/invoke-ui-changes-across-threads-on-vb-net/
in wpf such problems are easy to fix because you have a single thread for all the controls (Dispatcher.Invoke)
Update
dealing with the UI controls have to be on the UI thread
Me.Invoke(sub()
Dim t As TabPage = New TabPage((k + 1).ToString())
t.Name = k.ToString()
fff(t)
End Sub)
Me.Invoke(sub()
tcViewer.TabPages.Add(t)
End Sub)

Silverlight WCF - Invalid cross thread Exception with busy indicator

I am implementing WCF in Silverlight.
At the time of calling WCF service in silverlight, I would like to show 'BusyIndicator' to user.
I came across implementing busyindicator at this link.
This is in C#. And I am developing project in vb.net.
Could not correctly convert this to vb.net
busyIndicator.IsBusy = true;
//busyIndicator.BusyContent = "Fetching Data...";
ThreadPool.QueueUserWorkItem((state) =>
{
Thread.Sleep(3 * 1000);
Dispatcher.BeginInvoke(() => busyIndicator.IsBusy = false);
}
While implementing above code in my requirement
The code in VB.Net is as follows:
busyIndicator.IsBusy = True
ThreadPool.QueueUserWorkItem(Sub(state)
Dim s As New services.ServiceClient
AddHandler s.confirmticketCompleted, AddressOf mcompleted
s.confirmticketAsync()
Dispatcher.BeginInvoke(Sub()
busyIndicator.IsBusy = False
End Sub)
End Sub)
Sub mcompleted(sender As System.Object, e As services.confirmticketCompletedEventArgs)
MessageBox.Show(e.Result)
End Sub
Where services.serviceclient is WCF proxy with variable s
A completed event handler needs to be used to get return value.
After implementing the code I am getting `'Invalid cross-thread access.' exception at
MessageBox.Show(e.Result)
My question is similar to link available at StackOverflow
But I could not understand what Mr. Ken2k is saying.
Please guide and help me in solving the issue.
Wrap the MessageBox.Show() with the dispatcher Dispatcher.BeginInvoke(MessageBox.Show("BLAH") The service must be returning on a thread different to the UI thread, which is why you are getting the exception.
Got it...
I need to provide busyindicator.isbusy=false in completed function.
Remove ThreadPool.QueueUserWorkItem and Dispatcher.BeginInvoke altogether.
The final code looks like this:
busyIndicator.IsBusy = True
Dim s As New services.ServiceClient
AddHandler s.confirmticketCompleted, AddressOf mcompleted
s.confirmticketAsync()
Sub mcompleted(sender As System.Object, e As services.confirmticketCompletedEventArgs)
Dim k As String = e.Result
MessageBox.Show(k)
busyIndicator.IsBusy = False
End Sub
I really thank ken2k for this. The link at stackoverflow is link.
Thanq can be treated as closed question.

How to update main form from thread utilizing a module creates new mainform?

So my use for a module is so I can use the same functions across different programs that I develope for my employer. They also want my module to be distributed amongst other programmers so they can use it as well. The programs need to know when there is a thread still running (SQL code is running (there are no problems with the sql side) and it needs to notify the user when all work is done but the user needs to be able to queue work)
From the main form I am using this code:
Dim thread1 As New System.Threading.Thread(AddressOf ModuleTesting.Testing)
thread1.SetApartmentState(Threading.ApartmentState.STA)
thread1.IsBackground = True
thread1.Name = "ModuleLabelCrossThreading"
thread1.Start()
This is the code for my module:
Public Sub Testing()
Form1.threadsrunning += 1
Form1.accesscontrolsmoduletesting()
'THIS IS WHERE THE PROGRAM DOES STUFF ILLUSTRATED BY SLEEPING'
System.Threading.Thread.Sleep(2000)
Form1.threadsrunning -= 1
Form1.accesscontrolsmoduletesting()
end sub
The code to access the controls on the main form is
Public Sub accesscontrolsmoduletesting()
If Me.InvokeRequired = True Then
Me.Invoke(New MethodInvoker(AddressOf accesscontrolsmoduletesting))
Else
If threadsrunning > 0 Then
Label4.Text = threadsrunning & " threads running"
Else
Label4.Text = "0 threads running"
End If
End If
End Sub
I already know the issue is the new thread is creating a new form. I tested this by showing the form and making it wait so it didnt immediately dispose itself and I seen the label was updated. How do I make this thread update the main form instead of just creating a new mainform and then disposing itself after the thread dies?
To reiterate on my Comment you need to get the actual Form1 that is being shown, you should change your Testing Method to accept a Parameter of Form1, then you can use a Parameterized Thread.Start to pass in the Calling Form. You are running into a feature that was left in place to placate Vb6 programmers transitioning to VB.net as this answer by Hans states. And you may find this Blog Post by John Mcllhinney an interesting read.
From Second Link(emphasize mine):
In order to access a form from a secondary thread you generally need to test its InvokeRequired property and then call its Invoke method. I said earlier that there is only ever one default instance of a form class. That’s not strictly true. In fact, default instances are thread-specific, so there is only ever one default instance per thread. As such, if you test the InvokeRequired property of the default instance you will always be accessing the default instance for the current thread, not the one that was displayed on the main thread.
So in response to above I would change your Module Test Method to:
Public Sub Testing(myForm As Form1)
myForm.threadsrunning += 1
myForm.accesscontrolsmoduletesting()
'THIS IS WHERE THE PROGRAM DOES STUFF ILLUSTRATED BY SLEEPING'
System.Threading.Thread.Sleep(2000)
myForm.threadsrunning -= 1
myForm.accesscontrolsmoduletesting()
End Sub
And I would change your Form1's Thread Start Code to look like this.
Dim thread1 As New System.Threading.Thread(AddressOf ModuleTesting.Testing)
thread1.SetApartmentState(Threading.ApartmentState.STA)
thread1.IsBackground = True
thread1.Name = "ModuleLabelCrossThreading"
thread1.Start(Me) 'Note the passing in the instance of the calling Form
After making these few changes your code will work

Why am I getting a "Cross-thread operation not valid exception" in the callback method when using AsyncCallback with BeginInvoke?

I have a windows form that gets data from a scale via the serial port. Since I need the operator to be able to cancel the process I do the get data process on a second thread.
The data collection process gets multiple readings from the scale one at a time. The form has a label that needs to be updated with information specific to each reading.
I call the method to get the data from the scale with this code.
Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)
The delegate for the readALine method is defined in the UI code.
Delegate Function readALine(ByVal toSend As String) As String
The GetDataCallback method:
Private Sub GetDataCallback(ByVal ia As IAsyncResult)
.
.
.
lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
lblInstructions.Refresh()
.
.
.
End Sub
I get the exception on the "lblInstructions.Text =" statement.
I thought the GetDataCallback method was part of the UI thread so don't understand why I'm getting the exception. I know this could probably be rewritten using a BackgroundWorker and it's appropriate events but for now would like to understand why this isn't working as expected.
The application was written originally in VS2003 and just recently upgraded to VS2008.
Any insight will be appreciated.
Thanks,
Dave
The problem is a confusion over BeginInvoke. Calling Control.BeginInvoke marshals a delegate call to the UI thread. Calling BeginInvoke on a delegate causes it to be executed on a thread pool thread - and any callback you provide is executed on the same (thread pool) thread. The latter is what you're doing, which is why GetDataCallback is being executed on a thread pool thread, not the UI thread..
So, within GetDataCallback you need to call Control.Invoke or Control.BeginInvoke to marshal back to the UI thread.
One point to note: I very rarely use Control.InvokeRequired these days - it's simpler to unconditionally call Invoke/BeginInvoke; the performance difference isn't usually enough to make up for the benefit in readability, in my experience.
user-interface controls can only be updated by the thread that created them
try
yourForm.BeginInvoke
instead; that should marshall the call to the correct thread
In .NET 1.1 it was possible to perform these cross-thread operations, even though they weren't safe.
In .NET 2.0 the exception you mention is thrown, when you try to perform cross-thread operations such as this, which means that you are performing the UI operations on a non-UI thread, even though you thought you weren't.
Try using the Me.InvokeRequired method to determine whether you are currently on the UI thread. E.g. you could define a method to perform the necessary tasks:
Protected Sub PerformUIOperations()
If Me.InvokeRequired Then
'We are currently on a non-UI thread. Invoke this method on the UI thread:
Me.Invoke(New MethodInvoker(AddressOf Me.PerformUIOperations))
Return
End If
'Perform UI operations when we know it is safe:
lblInstructions.Text = "blabla"
End Sub
The PerformUIOperations method can then be called from any non-UI thread, since it takes care of performing the tasks on the correct thread itself.
Of course, if you need to pass parameters to the PerformUIOperations method (such as information regarding the ongoing operation) you'll have to define a delegate to match the PerformUIOperations method signature and use this instead of the MethodInvoker.
Jon,
I have the following in another section of the code:
Delegate Sub setValueCallback(ByVal row As Integer, ByVal value As Decimal)
Public Sub setValue(ByVal row As Integer, ByVal value As Decimal)
If Me.Controls.Item(_tbIndex(row - 1)).InvokeRequired Then
Dim d As New setValueCallback(AddressOf setValue)
Me.Invoke(d, New Object() {row, value})
Else
Dim tb As TextBox = Me.Controls.Item(_tbIndex(row - 1))
tb.Text = value
_dt.Rows(tb.Tag).Item(1) = value
End If
End Sub
How would this be rewritten to not use .InvokeRequired?
Dave, maybe this is the solution you are looking for:
Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)
Delegate Function readALine(ByVal toSend As String) As String
Private Sub GetDataCallback(ByVal ia As IAsyncResult)
If lblInstructions.InvokeRequired Then
lblInstructions.Invoke(New AsyncCallback(AddressOf GetDataCallback), New Object() {ia})
Else
.
.
lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
lblInstructions.Refresh()
.
.
End If
End Sub