Benutzer mit den meisten Antworten
Fehlermeldung: Ungültiger threadübergreifender Vorgang

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
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
- Als Antwort markiert Thorsten DörflerModerator Donnerstag, 2. Juni 2011 13:11
-
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,
Gruß Elmar
was aber das Formular während der Abfrage lahmlegt.
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
- Als Antwort markiert Thorsten DörflerModerator Donnerstag, 2. Juni 2011 13:11
-
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
-
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
-
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
-
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,
Gruß Elmar
was aber das Formular während der Abfrage lahmlegt.