none
Possible Cross Thread problem RRS feed

  • Question

  • Hi,

    I have a VB.NET application built with .Net 2.0 which is a call accounting software.
    I have created an asynchronous tcp connection to the PABX.
    Every time i receive data from the PABX, an event is raised and i try to parse the record.
    I can sometimes receive several records every second.

    Some weird things are happening like for example:
    - I have a line class which has:
    [CODE]
                id = getID()

                If id <> -1 Then
                    line_Id = id
                    line_Type = getLineType()
                Else
                    insertLine()
                    id = getID()
                    line_Id = id
                    line_Type = getLineType()
                End If

    [/CODE]

    [CODE]

    Private Function getID() As Long
            Dim strSQL As String
            Dim drsql As SqlClient.SqlDataReader
            strSQL = "SELECT line_Id from lines where line_Number='" & line_Name & "' and site_id=" & siteid
            drsql = selectCMD(strSQL)
            If drsql.HasRows = True Then
                drsql.Read()
                getID = drsql("line_Id")
            Else
                getID = -1
            End If
            drsql.Close()
        End Function

        Private Sub insertLine()
            Dim strSQL As String
            strSQL = "INSERT INTO lines (line_Number,line_name,site_id) VALUES ('" & line_Name & "','" & line_Name & "'," & siteid & ")"
            insertCMD(strSQL)
        End Sub

    [/CODE]

    The code checks to see if the line exists before adding a new one but after several days, i check the database and i notice several lines which are duplicated.
    I'm 100% sure that the class is running correctly.

    - On to another problem:
    I sometimes get this error:
    [CODE]
    Error in SELECT statement
    SELECT line_Id from lines where line_Number='414' and site_id=1
    System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.

    [/CODE]
    But every DataReader is closed correctly and i don't receive this error everytime.

    But the weirdest error is this:
    [CODE]
    Error in SELECT statement
    SELECT line_Id from lines where line_Number='406' and site_id=1
    System.Data.SqlClient.SqlException: The conversion of a char data type to a datetime data type resulted in an out-of-range datetime value.

    [/CODE]
    There is no date in that select and the try/catch is wrapped around the select command only.


    Due to these errors, i was drawn to believe that something is being mixed up due to several records arriving every second.

    I have set Control.CheckForIllegalCrossThreadCalls = False, might this be letting threads cross which each other and messing things up?
    Will each time the onDataArrival event is raised, a new thread is created? and is the CheckForIllegalCrossThreadCalls responsible for these problems?
    or it is something else?

    Thank You.
    Tuesday, August 26, 2008 3:44 PM

Answers

  • The first problem is caused because you may have a line record being inserted in one thread in the time between another thread checking for existence of the row and trying the insert.

    There are 2 ways around this, firstly you could put a lock in your code so that only one thread can enter that whole routine (i.e. all code from the select to the insert) at a time (http://msdn.microsoft.com/en-us/library/ms173179(VS.80).aspx). Or you could use SQL server to do the locking for you inside a transaction. 

    There's some information here about sql server locking, although I'm not sure how current it is: http://www.samsaffron.com/blog/archive/2007/04/04/14.aspx (you may need a stored proc).

    I'm not sure about your second problem, but it would be interesting to see the code in selectCMD(). Are you returning the same command instance? Are you disposing the command object?

    The third problem I've had before and I too didn't have a date, unfortunately I can't for the life of me remember what it was, but I'll let you know if/when I remember.


    EDIT: Sorry - just checked my emails and when I had the "out-of-range datetime value" error it was to do with a datetime column and running in different countries.
    • Edited by Laughing John Tuesday, August 26, 2008 11:06 PM added link to msdn
    • Marked as answer by HaniBey Wednesday, August 27, 2008 8:03 AM
    Tuesday, August 26, 2008 10:43 PM
  • You'll need to start using SyncLock.  And of course you'll have to remove Control.CheckForIllegalCrossThreadCalls = False.  You threw away a great diagnostic tool. 
    Hans Passant.
    • Marked as answer by HaniBey Wednesday, August 27, 2008 8:03 AM
    Wednesday, August 27, 2008 5:00 AM
    Moderator
  • To update your form control you can use Control.InvokeRequired() to test if you are running on a thread other than the GUI (message queue) thread. If you are then you use Control.Invoke to run the update code on the same thread as the GUI, otherwise you can just call it normally.

    http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx
    http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx

    Some example code here:

    http://www.dotnetjunkies.com/WebLog/bbenbachir/archive/2006/04/29/137586.aspx




    • Marked as answer by HaniBey Wednesday, August 27, 2008 11:15 AM
    Wednesday, August 27, 2008 9:19 AM

All replies

  • The first problem is caused because you may have a line record being inserted in one thread in the time between another thread checking for existence of the row and trying the insert.

    There are 2 ways around this, firstly you could put a lock in your code so that only one thread can enter that whole routine (i.e. all code from the select to the insert) at a time (http://msdn.microsoft.com/en-us/library/ms173179(VS.80).aspx). Or you could use SQL server to do the locking for you inside a transaction. 

    There's some information here about sql server locking, although I'm not sure how current it is: http://www.samsaffron.com/blog/archive/2007/04/04/14.aspx (you may need a stored proc).

    I'm not sure about your second problem, but it would be interesting to see the code in selectCMD(). Are you returning the same command instance? Are you disposing the command object?

    The third problem I've had before and I too didn't have a date, unfortunately I can't for the life of me remember what it was, but I'll let you know if/when I remember.


    EDIT: Sorry - just checked my emails and when I had the "out-of-range datetime value" error it was to do with a datetime column and running in different countries.
    • Edited by Laughing John Tuesday, August 26, 2008 11:06 PM added link to msdn
    • Marked as answer by HaniBey Wednesday, August 27, 2008 8:03 AM
    Tuesday, August 26, 2008 10:43 PM
  • You'll need to start using SyncLock.  And of course you'll have to remove Control.CheckForIllegalCrossThreadCalls = False.  You threw away a great diagnostic tool. 
    Hans Passant.
    • Marked as answer by HaniBey Wednesday, August 27, 2008 8:03 AM
    Wednesday, August 27, 2008 5:00 AM
    Moderator
  • Thank you John.

    I can have several incoming connections to my app so I have done some testing and put thread.sleep inside my parsing code and i noticed that if data are coming from one connection, no matter how fast they are, then they'll be queued but if data are coming from several connections then they'll access the same parsing code at the same time which is why we weren't having this issue before (had only one PABX).

    As for the second problem, here is my code:
    1    Public Sub insertCMD(ByVal strSQL As String) 
    2        Try 
    3            If cnSQL.State = ConnectionState.Open Then 
    4                Dim cmdSQL As New SqlCommand(strSQL, cnSQL) 
    5                cmdSQL.CommandTimeout = 120 
    6                cmdSQL.ExecuteNonQuery() 
    7            End If 
    8        Catch e As Exception 
    9            Dim exString As String 
    10            exString = "error in INSERT statement:" & vbCrLf & e.ToString & vbCrLf & strSQL & "." 
    11            log(exString, EventLogEntryType.Error, 107) 
    12        End Try 
    13    End Sub 
    14 
    15    Public Function selectCMD(ByVal strSQL As String) As SqlDataReader 
    16        Try 
    17            If cnSQL.State = ConnectionState.Open Then 
    18                Dim cmdSQL As New SqlCommand(strSQL, cnSQL) 
    19                cmdSQL.CommandTimeout = 120 
    20                selectCMD = cmdSQL.ExecuteReader 
    21               
    22            Else 
    23                If openConnection() = True Then 
    24                    selectCMD = selectCMD(strSQL) 
    25                Else 
    26                    Return Nothing 
    27                End If 
    28            End If 
    29        Catch ex As Exception 
    30            Dim exString As String 
    31            exString = "Error in SELECT statement " & vbCrLf & strSQL & vbCrLf & ex.ToString & "." 
    32            log(exString, EventLogEntryType.Error, 108) 
    33            selectCMD = Nothing 
    34        End Try 
    35    End Function 
    36 

    I would say that both threads coming from the 2 connections are accessing these same functions and generating the problem.


    As for the third problem, i had that problem in another place (a logical place for it to appear) and i solved it, but the weird thing was where it appeared. Since that select command doesn't contain a date nor the whole lines table and as you can see from the above code, the try/catch is around the select only.
    So the sql string must have came from one thread and the exception string came from another thread.


    I'm gonna try putting my whole parsing code inside a SyncLock and hopefully the errors will disappear.

    The reason why we added Control.CheckForIllegalCrossThreadCalls = False was because we were trying to update the main form controls from the other threads and this fix seemed to be the easiest.
    I don't know what are the full effects CheckForIllegalCrossThreadCalls has on my code but i'm gonna remove and fix the control access in another method.

    Sorry if this should be obvious, but i've never dealt with threads too much or read about them a lot so it is kinda new to me.

    Thanks again.
    • Edited by HaniBey Wednesday, August 27, 2008 7:12 AM Added something.
    Wednesday, August 27, 2008 7:09 AM
  • To update your form control you can use Control.InvokeRequired() to test if you are running on a thread other than the GUI (message queue) thread. If you are then you use Control.Invoke to run the update code on the same thread as the GUI, otherwise you can just call it normally.

    http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx
    http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx

    Some example code here:

    http://www.dotnetjunkies.com/WebLog/bbenbachir/archive/2006/04/29/137586.aspx




    • Marked as answer by HaniBey Wednesday, August 27, 2008 11:15 AM
    Wednesday, August 27, 2008 9:19 AM
  • Thanks a lot, again.
    Wednesday, August 27, 2008 11:15 AM