none
Fehlermeldung: Ungültiger threadübergreifender Vorgang RRS feed

  • Frage

  • Hallo zusammen,

    ich habe hier ein Problem, das ich trotz fleissigem googelns nicht wirklich lösen kann.

    VB 2008

    Hintergrund:
    Ich entwickele eine Applikation, die bei einem Telefonanruf den Anrufer über die Telefonnummer identifiziert und aus einer Datenbank die gefundenen Daten in eine Eingabemaske übertragen soll. (Klingt erstmal nicht so kompliziert)

    Um die Telefonnummer des Anrufers zu erhalten, verwende ich die TAPI-Klasse von julmar.com. Funktioniert auch einwandfrei.

    Mein Problem:

    Ich bilde eine Instanz der TAPI-Klasse und "warte" auf einen Anruf.
    Kommt ein Anruf rein, erhalte ich über die Klasse die Telefonnummer und suche jetzt die dazugehörigen Daten in der Datenbank.
    Wenn ich die Daten gefunden habe, möchte ich die Daten in (sehr) viele Textfelder auf meinem Formular eintragen.
    Hierbei kommt es dann zu der Fehlermeldung: Ungültiger threadübergreifender Vorgang.

    Nach einigem googeln, weiß ich jetzt ungefähr, was diese Fehlermeldung bedeutet, kann aber keine wirkliche Lösung entdecken.

    In den diversen Foren wird gesagt man soll in den Eigenschaften des Textfeldes eine Einstellung vornehmen um die Threadsicherheit auszuschalten. Hier wird aber immer nur von einem Textfeld gesprochen, in meiner Anwendung geht es aber um ca. 30 Textfelder, so dass ich diese Lösung irgendwie anzweifele.

    Ich bedanke mich im vorraus für jede Unterstützunge.

    Viele Grüße
    Gernot

    Montag, 30. Mai 2011 19:25

Antworten

  • Hallo Gernot,

    auch wenn die klassischen einfachen Beispiele wie:
    Gewusst wie: Threadsicheres Aufrufen von Windows Forms-Steuerelementen
    sich auf ein einziges Steuerelement beschränken, so ist das nicht zwingend.

    Der Test auf InvokeRequired muss nur einmalig erfolgen -
    in solchen Fällen kann man dafür z. B. die Formular-Instanz nehmen.
    Im Invoke Callback darfst Du beliebige Steuerelemente anpacken,
    denn dann befindest Du Dich auf dem "richtigen" GUI-Thread.

    Gruß Elmar

    Montag, 30. Mai 2011 22:38
    Beantworter
  • Hallo Gernot,

    auf das wesentliche runtergebrochen, könnte das dann so aussehen -
    wie man sieht halb so wild:

      Dim WithEvents myTapi As TapiManager
    
      Private Sub frmCallin_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' Zum Vergleich die Thread Id
        Console.WriteLine("Form Thread ID: {0}", Threading.Thread.CurrentThread.ManagedThreadId)
    
        ' Aufruf für CTI
        myTapi = New TapiManager("Phone.Net")
        ' wenn die Initalizierung nicht statt gefunden hat -> Routine verlassen
        ' ...
      End Sub
    
      Public Sub OnNewCall(ByVal sender As Object, ByVal e As NewCallEventArgs) Handles myTapi.NewCall
        ' Thread des TAPI Managers
        Console.WriteLine("OnNewCall Thread ID: {0}", Threading.Thread.CurrentThread.ManagedThreadId)
    
        Dim Mandanteninfo As DataSet
        Mandanteninfo = Mandant_ermitteln(e.Call.CalledId.ToString)
    
        Me.Invoke(New Action(Of DataSet)(AddressOf MandantenInfoAusDataSet), Mandanteninfo)
    
        ' Die lange Version, kann entfallen, da hier InvokeRequired ohnehin True
        'If Me.InvokeRequired Then
        '  Me.Invoke(New Action(Of DataSet)(AddressOf MandantenInfoAusDataSet), Mandanteninfo)
        'Else
        '  MandantenInfoAusDataSet(Mandanteninfo)
        'End If
      End Sub
    
      Private Sub MandantenInfoAusDataSet(ByVal mandantenInfo As DataSet)
        ' Wäre hier die gleiche wie in Form_Load
        Console.WriteLine("MandantenInfoAusDataSet Thread ID: {0}", Threading.Thread.CurrentThread.ManagedThreadId)
    
        Me.Button1.Visible = True
        Me.txtVorname.Text = "Vorname aus dem Dataset"
        Me.txtNachname.Text = "Nachname aus dem Dataset"
      End Sub
    
      Private Function Mandant_ermitteln(ByVal callid As String) As DataSet
        ' Dummy DataSet
        Return New DataSet("MandantenInfo")
      End Function
    
    
    

    Auskommentiert habe ich die Variante stehen lassen, wie man sie in der MSDN und anderswo findet.
    Hier kann man aber darauf verzichten InvokeRequired zu testen, da der TapiManager seine
    Rückrufe ohnehin über einen anderen (Hintergrund-)Thread ausführt. Womit ein Invoke unumgänglich wird.

    Zur Verdeutlichung habe ich einige Console.WriteLine eingebaut, wo Du die ThreadId sehen solltest.

    Das Zuweisen ist in eine eigene Prozedur MandantenInfoAusDataSet gewandert,
    der das MandantenInfo-DataSet übergeben wird.
    Dort befindet man sich durch das Invoke wieder auf dem GUI Thread.

    Beachten musst Du,dass Mandant_ermitteln (bei mir nur ein Dummy) auf dem Hintergrund-Thread ausgeführt wird.
    Wenn Du dabei auf gemeinsame Variabeln wie z. B. eine Connection zugreifst kann
    das u. U. zu Überschneidungen führen, wenn gleichzeitig ein neuer Anruf eingeht.
    In der Routine solltest Du desegen eine neue Datenbank-Verbindung erzeugen
    und auch nicht auf andere gemeinsame Variabeln zugreifen.

    Alternativ könnte man das ebenso in den Vordergrund-Thread legen,
    was aber das Formular während der Abfrage lahmlegt.

    Gruß Elmar
    Dienstag, 31. Mai 2011 11:54
    Beantworter

Alle Antworten

  • Hallo Gernot,

    auch wenn die klassischen einfachen Beispiele wie:
    Gewusst wie: Threadsicheres Aufrufen von Windows Forms-Steuerelementen
    sich auf ein einziges Steuerelement beschränken, so ist das nicht zwingend.

    Der Test auf InvokeRequired muss nur einmalig erfolgen -
    in solchen Fällen kann man dafür z. B. die Formular-Instanz nehmen.
    Im Invoke Callback darfst Du beliebige Steuerelemente anpacken,
    denn dann befindest Du Dich auf dem "richtigen" GUI-Thread.

    Gruß Elmar

    Montag, 30. Mai 2011 22:38
    Beantworter
  • Hallo Elmar,

    vielen Dank für Deine freundliche Hilfe.

    Den von Dir genannten Link habe ich mir angeschaut, ohne wirklich schlau daraus zu werden. Irgendwie scheine ich Schwierigkeiten zu haben, mit diesem Thema klar zu kommen.

    Ich wäre Dir sehr dankbar, wenn Du mir ein Stückchen Quellcode zukommen lassen könntest, wie ich das ganze über eine komplette Form handeln kann.

    Vielen Dank für Deine Mühe.

    Gernot

    Dienstag, 31. Mai 2011 07:35
  • Hallo Gernot,

    Du bist nicht der erste der anfangs damit Probleme hat.

    Es wäre sinnvoller, Du würdest den derzeit verwendeten Code ausschnittsweise posten
    (dort wo Du die ca. 30 Textboxen fütterst, reichen eine Handvoll).

    Dann kannst Du es wahrscheinlich besser nachvollziehen, als wenn ich ein weiteres
    synthetisches Beispiel poste.

    Gruß Elmar

    Dienstag, 31. Mai 2011 07:57
    Beantworter
  • Hallo Elmar,

    an Code soll es nicht liegen :-)

    Private Sub frmCallin_Load(ByVal sender As System.Object, ByVal e As _
        System.EventArgs) Handles MyBase.Load
    
          ' Aufruf für CTI
          myTAPI = New TapiManager("Phone.Net")
          ' wenn die Initalizierung nicht statt gefunden hat -> Routine verlassen
          If Not myTAPI.Initialize Then
            Exit Sub
          Else
            Dim lineArray As TapiLine() = myTAPI.Lines
            Dim i As Integer = 0
    
            'verfügbare TAPI-Lines in Array wegschreiben
            For i = 0 To lineArray.Length - 1
              Dim line As TapiLine = lineArray(i)
              If InStr(UCase(Trim(line.Name)), UCase(AP_CTI.ToString)) Then
                'Monitor für die in den Clienteigeschaften hinterlegte Line starten
                line.Monitor()
              End If
            Next
          End If
        End If
      End Sub
    
    Public Sub OnNewCall(ByVal sender As Object, ByVal e As NewCallEventArgs) Handles myTAPI.NewCall
        Dim Mandanteninfo As DataSet
    
        Mandanteninfo = Mandant_ermitteln(e.Call.CalledId.ToString)
    
        'Nachfolgende Zeilen werden jeweils mit dem genannten Fehler quittiert
    
        Me.Button1.Visible = True
        Me.txtVorname.txt="Vorname aus dem Dataset"
        me.txtNachname.txt="Nachname aus dem Dataset"
        '....und viele, viele Textfelder mehr :-)
    
      End Sub
    
    

    Ich hoffe, Du kannst etwas mit meine Quellcode anfangen,... ich habe ihn noch ein bisschen bereinigt.

    Vielen Dank!

    Gernot

    Dienstag, 31. Mai 2011 09:09
  • Hallo Gernot,

    auf das wesentliche runtergebrochen, könnte das dann so aussehen -
    wie man sieht halb so wild:

      Dim WithEvents myTapi As TapiManager
    
      Private Sub frmCallin_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' Zum Vergleich die Thread Id
        Console.WriteLine("Form Thread ID: {0}", Threading.Thread.CurrentThread.ManagedThreadId)
    
        ' Aufruf für CTI
        myTapi = New TapiManager("Phone.Net")
        ' wenn die Initalizierung nicht statt gefunden hat -> Routine verlassen
        ' ...
      End Sub
    
      Public Sub OnNewCall(ByVal sender As Object, ByVal e As NewCallEventArgs) Handles myTapi.NewCall
        ' Thread des TAPI Managers
        Console.WriteLine("OnNewCall Thread ID: {0}", Threading.Thread.CurrentThread.ManagedThreadId)
    
        Dim Mandanteninfo As DataSet
        Mandanteninfo = Mandant_ermitteln(e.Call.CalledId.ToString)
    
        Me.Invoke(New Action(Of DataSet)(AddressOf MandantenInfoAusDataSet), Mandanteninfo)
    
        ' Die lange Version, kann entfallen, da hier InvokeRequired ohnehin True
        'If Me.InvokeRequired Then
        '  Me.Invoke(New Action(Of DataSet)(AddressOf MandantenInfoAusDataSet), Mandanteninfo)
        'Else
        '  MandantenInfoAusDataSet(Mandanteninfo)
        'End If
      End Sub
    
      Private Sub MandantenInfoAusDataSet(ByVal mandantenInfo As DataSet)
        ' Wäre hier die gleiche wie in Form_Load
        Console.WriteLine("MandantenInfoAusDataSet Thread ID: {0}", Threading.Thread.CurrentThread.ManagedThreadId)
    
        Me.Button1.Visible = True
        Me.txtVorname.Text = "Vorname aus dem Dataset"
        Me.txtNachname.Text = "Nachname aus dem Dataset"
      End Sub
    
      Private Function Mandant_ermitteln(ByVal callid As String) As DataSet
        ' Dummy DataSet
        Return New DataSet("MandantenInfo")
      End Function
    
    
    

    Auskommentiert habe ich die Variante stehen lassen, wie man sie in der MSDN und anderswo findet.
    Hier kann man aber darauf verzichten InvokeRequired zu testen, da der TapiManager seine
    Rückrufe ohnehin über einen anderen (Hintergrund-)Thread ausführt. Womit ein Invoke unumgänglich wird.

    Zur Verdeutlichung habe ich einige Console.WriteLine eingebaut, wo Du die ThreadId sehen solltest.

    Das Zuweisen ist in eine eigene Prozedur MandantenInfoAusDataSet gewandert,
    der das MandantenInfo-DataSet übergeben wird.
    Dort befindet man sich durch das Invoke wieder auf dem GUI Thread.

    Beachten musst Du,dass Mandant_ermitteln (bei mir nur ein Dummy) auf dem Hintergrund-Thread ausgeführt wird.
    Wenn Du dabei auf gemeinsame Variabeln wie z. B. eine Connection zugreifst kann
    das u. U. zu Überschneidungen führen, wenn gleichzeitig ein neuer Anruf eingeht.
    In der Routine solltest Du desegen eine neue Datenbank-Verbindung erzeugen
    und auch nicht auf andere gemeinsame Variabeln zugreifen.

    Alternativ könnte man das ebenso in den Vordergrund-Thread legen,
    was aber das Formular während der Abfrage lahmlegt.

    Gruß Elmar
    Dienstag, 31. Mai 2011 11:54
    Beantworter
  • Hallo Elmar,

    vielen Dank für Deinen ausführlichen (und gut dokumentierten) Quellcode.

    Ich werde das ganze hier mal umsetzen und bin zuversichtlich das es auch funktionieren wird.

    Also nochmals: Vielen Dank!

    ...und schöne Grüße

    Gernot

    Mittwoch, 1. Juni 2011 09:23