WCF - Latenzproblem beim ersten Aufruf einer Meth

Hi!

Ich beschäftige mich erst seit meinem Urlaub letzte Woche mit WCF (3.0 in VB/VS2005).

Ich verwende TCP-Binding, ohne Sicherheit und ohne Verschlüsselung. Übers Netzwerk hab ich eine Latenz von etwa 0,3 ms bis 1,0 ms, je nach Komplexität und Größe der zu transferierenden Objekte. Mein Problem ist nun, dass der erste Aufruf jeder einzelnen Methode meines Proxies wesentlich länger dauert, zeilweise 500 ms oder länger.

Kann ich irgendwelche Optimierungen einstellen?
Kann mir jemand gute Internetseiten zu dem Thema empfehlen?

MfG,
Fred.

Hallo!

Nur „WCF“ ist ein wenig dürftig, wenn Du nähere Angaben haben willst, warum Deine Serverobjekte soundsolange für eine bestimmte Aktion brauchen.
Wie sind Deine Serverobjekte denn angelegt? Singleton? SingleCall? CAOs? Sonstiges?

Allgemein kann man nur sagen, dass unter der Oberfläche eine ganze Menge Voodoo getrieben wird, damit Du ohne viel Aufwand auf die Serverobjekte zugreifen kannst (Anlegen und initialisieren der Channels, MessageSinks, Proxies usw.). Darum bin ich nicht wirklich überrascht davon, dass der erste Aufruf jeweils länger dauert als dann im laufenden Betrieb, wenn die ganze Infrastruktur schon steht…

Gruß,
Martin

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Hi!

Ich bin jetzt erst dazu gekommen, das Problem weiter zu untersuchen. Folgendes konnte ich herausfinden: Der „Channel“ im Hintergrund scheint exakt nach 15 Sekunden beendet/pausiert zu werden und wird bei erneuter Verwendung neu aufgebaut/aktiviert. Dies scheint die erhöhte Latenz zu verursachen.

Hier nun der Code:

Der Contract:

 \_
 Public Interface [Interface]

 \_
 Function Counter() As System.Int32

 End Interface

Meine Client-Klasse:

 Public Class Client
 Inherits ClientBase(Of [Interface])

 Implements [Interface]

 Public Sub New(ByVal Binding As Channels.Binding, ByVal RemoteAddress As EndpointAddress)
 Call MyBase.New(Binding, RemoteAddress)
 End Sub

 Public Function Counter() As System.Int32 Implements [Interface].Counter
 Return Me.Channel.Counter
 End Function
 End Class

Hier der Host für die Server-Seite:

 Friend Class Host
 Inherits Threading.Disposable

 Private Host As ServiceHost

 Public Sub New()
 Call MyBase.New()

 Call Trace.WriteLine("Starting Host.")

 Dim Ex As System.Exception = Nothing
 Try
 Me.Host = New ServiceHost(GetType([Class]))

 Dim Binding As New NetTcpBinding(SecurityMode.None)
 Call Me.Host.AddServiceEndpoint(GetType([Interface]), Binding, "net.tcp://localhost:15781/LCS")
 Call Me.Host.Open()
 Catch iEx As Exception
 Ex = iEx
 Me.Host = Nothing
 End Try

 If Ex Is Nothing Then
 Call Trace.WriteLine("Host is now running.")
 Else
 Call Me.Dispose()
 Call Trace.WriteLine(Ex, "Starting Host")

 Throw Ex
 End If
 End Sub

 Protected Overrides Sub iiDispose()
 If Me.Host IsNot Nothing Then
 'Call Me.Host.Abort()
 Call Me.Host.Close()
 Me.Host = Nothing
 End If

 Call Trace.WriteLine("Disposing Host")
 End Sub
 End Class

Und hier die Server-Session:

 \_
 Friend Class [Class]
 Implements [Interface]

 Private ReadOnly LockObject As System.Object
 Private iCounter As System.Int32

 Public Sub New()
 Me.LockObject = Core.Type.Format(Me.GetType)
 Me.iCounter = 0
 End Sub

 Public Function Counter() As System.Int32 Implements [Interface].Counter
 Dim Erg As System.Int32 = 0

 SyncLock Me.LockObject
 Me.iCounter += 1
 Erg = Me.iCounter
 End SyncLock

 Return Erg
 End Function
 End Class

So starte ich den Proxy auf der Client-Seite:

 Dim Binding As New NetTcpBinding(SecurityMode.None)
 Dim Address As New EndpointAddress("net.tcp://localhost:15781/LCS")

 Me.cmdStart.Enabled = False

 Try
 Me.Proxy = New LimitCheckService.Client(Binding, Address)
 Call Me.Proxy.Open()

 Me.cmdStop.Enabled = True
 Catch Ex As System.Exception
 Me.cmdStart.Enabled = True
 End Try

Hier meine Test-Methode, die in einem BackGround-Worker läuft:

 Dim Thread As Threading.Thread = Threading.Thread.Join("TestWorker")
 Dim CurrentSleepDuration As System.Double = 14

 Dim ts\_A As Core.TimeCode = Nothing
 Dim Counter As System.Int32 = Nothing
 Dim ts\_B As Core.TimeCode = Nothing

 Dim LastResponseTime As Core.TimeCode = Time()

 'Die ersten Aufrufe ungemessen ausführen, da diese westentlich langsamer laufen als die späteren.
 For d As System.Int32 = 1 To 10
 MyCounter += 1
 Counter = Me.Proxy.Counter
 Next d

 Do
 If Me.TestWorker.CancellationPending Then
 e.Cancel = True
 Exit Do
 End If

 MyCounter += 1

 ts\_A = Time()
 Counter = Me.Proxy.Counter
 ts\_B = Time()

 Call Trace.WriteLine(System.String.Format(" The Operation took {1} ms after waiting {2} s. MyCounter: {3} ServerCounter: {4}", Time, (Core.TimeCode.DeltaInSeconds(ts\_A, ts\_B) \* 1000).ToString("0.000"), Core.TimeCode.DeltaInSeconds(LastResponseTime, ts\_A).ToString("0.000"), MyCounter, Counter))
 LastResponseTime = ts\_B

 CurrentSleepDuration += 0.1

 ts\_A = Time.AddSeconds(CurrentSleepDuration)
 Do Until Time() \>= ts\_A
 If Me.TestWorker.CancellationPending Then
 e.Cancel = True
 Exit Do
 End If

 Call Thread.Sleep(40)
 Loop
 Loop Until e.Cancel

 Call Thread.Dispose()

Das ganze liefert diese Ausgabe:

 The Operation took 0,109 ms after waiting 0,011 s. MyCounter: 11 ServerCounter: 11
 The Operation took 0,419 ms after waiting 14,103 s. MyCounter: 12 ServerCounter: 12
 The Operation took 0,376 ms after waiting 14,216 s. MyCounter: 13 ServerCounter: 13
 The Operation took 0,372 ms after waiting 14,335 s. MyCounter: 14 ServerCounter: 14
 The Operation took 0,380 ms after waiting 14,419 s. MyCounter: 15 ServerCounter: 15
 The Operation took 0,376 ms after waiting 14,535 s. MyCounter: 16 ServerCounter: 16
 The Operation took 0,441 ms after waiting 14,615 s. MyCounter: 17 ServerCounter: 17
 The Operation took 0,462 ms after waiting 14,737 s. MyCounter: 18 ServerCounter: 18
 The Operation took 0,448 ms after waiting 14,817 s. MyCounter: 19 ServerCounter: 19
 The Operation took 0,376 ms after waiting 14,937 s. MyCounter: 20 ServerCounter: 20
 The Operation took 340,351 ms after waiting 15,016 s. MyCounter: 21 ServerCounter: 21

Ab einer „Sendepause“ von 15 Sekunden, also einer Zeit in der in meinem Fall die Methode „Counter“ nicht aufgerufen wird, erhöht sich die Latenz. Und es sind immer 15 Sekunden. Es speilt keine Rolle, wieviele Aufrufe ich vorher durchführe o.ä. Ab 15 Sekunden Stille scheint WCF die Verbindung zu beenden oder zu pausieren.

Gibt es eine Option, mit der ich einstellen kann, wann der Channel pausiert werden soll? Besser noch, kann ich auf irgendeine Weise garantieren, dass alle Methoden aktiv bleiben, bis der Client beendet wird (also per Proxy.Close geschlossen wird)?

Die Anwendung die ich plane, besteht aus wenigen Clients (weniger als 20), diese halten die Verbindung aber evtl. über Tage hinweg. Im späteren produktiven Einsatz wird es nur selten Vorkommen, dass eine Methode länger als 15 Sekunden nicht benutzt wird, aber eine Latenz von 300 ms ist nicht akzeptabel. Eine Latzen von etwa 3 bis 5 ms wäre noch ok.

Schon jetzt besten dank für das Interesse.
Frederik.

Hi!
Ich habe jetzt zwar den Code nicht explizit ausprobiert und bin mit WCF noch nicht so vertraut, aber beim „Vorläufer“ .NET Remoting konnte man Einfluss auf die Lebenszeit von Remoting-Serverobjekten nehmen (durch Überschreiben von MarshalByRefObject.InitializeLifetimeService - vermutlich bei WCF auch).
Diese war standardmäßig begrenzt, woraufhin das Objekt nach einer gewissen Zeit Inaktivität freigegeben wurde und beim nächsten Aufruf wieder erstellt werden musste.
Ich vermute, so ein Effekt schlägt bei Dir auch zu. Leider kann ich Dir aus dem Stand nicht sagen, an welchem Attribut Du dabei drehen musst, aber vielleicht hilft ja schon der „Schubs“ in diese Richtung, damit Du weisst, wo in der Dokumentation Du nachsehen kannst.

Gruß,
Martin