2010-04-08 6 views
8

Hier ist meine Situation. Ich habe einen WCF-Dienst geschrieben, der eine der Code-Basen unseres Anbieters aufruft, um Vorgänge wie Anmelden, Abmelden etc. auszuführen. Eine Voraussetzung für diesen Vorgang ist, dass wir einen Hintergrund-Thread haben, um Ereignisse als Ergebnis dieser Aktion zu erhalten. Beispielsweise wird die Login-Aktion für den Hauptthread gesendet. Dann werden mehrere Ereignisse von dem Vendor-Service als Ergebnis der Anmeldung zurück empfangen. Es können 1, 2 oder mehrere Ereignisse empfangen werden. Der Hintergrundthread, der auf einem Timer ausgeführt wird, empfängt diese Ereignisse und löst ein Ereignis im Dienst "wcf" aus, um zu benachrichtigen, dass ein neues Ereignis eingetroffen ist.WCF-Dienst mit Rückrufen aus dem Hintergrund Thread?

Ich habe den WCF-Dienst im Duplex-Modus implementiert und geplant, Callbacks zu verwenden, um der Benutzeroberfläche mitzuteilen, dass Ereignisse eingetroffen sind. Hier ist meine Frage: Wie sende ich neue Ereignisse aus dem Hintergrund-Thread an den Thread, der den Dienst ausführt?

Gerade jetzt, wenn ich OperationContext.Current.GetCallbackChannel<IMyCallback>() aufrufen, ist der OperationContext null. Gibt es ein Standardmuster, um dies zu umgehen?

Ich benutze PerSession als meine SessionMode auf dem ServiceContract.

UPDATE: Ich dachte, ich würde mein genaues Szenario klarer machen, indem ich demonstriere, wie ich Ereignisse vom Verkäufercode empfange. Meine Bibliothek empfängt jedes Ereignis, bestimmt das Ereignis und löst ein Ereignis für dieses bestimmte Ereignis aus.

Ich habe ein anderes Projekt, das eine Klassenbibliothek speziell für die Verbindung mit dem Anbieter-Service ist. Ich werde die gesamte Implementierung des Dienstes veröffentlichen, ein klareres Bild zu geben:

[ServiceBehavior(
     InstanceContextMode = InstanceContextMode.PerSession 
     )] 
    public class VendorServer:IVendorServer 
    { 
private IVendorService _vendorService; // This is the reference to my class library 

     public VendorServer() 
     { 
_vendorServer = new VendorServer(); 
_vendorServer.AgentManager.AgentLoggedIn += AgentManager_AgentLoggedIn; // This is the eventhandler for the event which arrives from a background thread 

} 

     public void Login(string userName, string password, string stationId) 
     { 
      _vendorService.Login(userName, password, stationId); // This is a direct call from the main thread to the vendor service to log in 
     } 

    private void AgentManager_AgentLoggedIn(object sender, EventArgs e) 
    { 

     var agentEvent = new AgentEvent 
          { 
           AgentEventType = AgentEventType.Login, 
           EventArgs = e 
          }; 
    } 
} 

Das AgentEvent Objekt enthält den Rückruf als eine ihrer Eigenschaften, und ich dachte, dass ich den Rückruf wie folgt durchführen würde:

agentEvent.Callback = OperationContext.Current.GetCallbackChannel<ICallback>(); 

die AgentEvent ist ein Objekt im Dienst definiert:

0123:

[DataContract] 
public class AgentEvent 
{ 
    [DataMember] 
    public EventArgs EventArgs { get; set; } 
    [DataMember] 
    public AgentEventType AgentEventType { get; set; } 
    [DataMember] 
    public DateTime TimeStamp{ get; set; } 
    [DataMember] 
    public IVendorCallback Callback { get; set; } 
} 

IVendorCallback wie folgt aussieht

public interface IVendorCallback 
    { 
     [OperationContract(IsOneWay = true)] 
     void SendEvent(AgentEvent agentEvent); 
    } 

Der Rückruf wird auf dem Client implementiert und verwendet die EventArgs-Funktion des AgentEvent zum Auffüllen von Daten auf der Benutzeroberfläche. Wie würde ich die OperationContext.Current-Instanz vom Hauptthread in den Hintergrundthread übergeben?

+0

Es gibt nicht genug Informationen in Ihrem Update, um wirklich zu verstehen, was vor sich geht. Wo befindet sich dieser Code? Wie machst du den "Login"? Ist es eine Art asynchrone Methode? Was passiert mit dem 'AgentEvent' (wo wird es eigentlich verwendet)? Und was ist der Typ von 'agentEvent.Callback' und wie/wann wird der Wert verwendet? – Aaronaught

+0

Ich habe ein vollständigeres Beispiel von dem, was ich versuche, hinzugefügt. Die Ereignisse, die ich erhalte, werden von der Bibliothek ausgelöst, die ich innerhalb des Dienstes verwende, weshalb ich sie abonnieren muss. –

Antwort

5

OperationContext.Current ist nur für den Thread verfügbar, der den Vorgang tatsächlich ausführt. Wenn Sie möchten, dass es für einen Arbeitsthread verfügbar ist, müssen Sie einen Verweis auf den Callback-Kanal an diesen Thread übergeben.

So könnte Ihr Betrieb etwas aussehen vage wie:

public class MyService : IMyService 
{ 
    public void Login() 
    { 
     var callback = 
      OperationContext.Current.GetCallbackChannel<ILoginCallback>(); 
     ThreadPool.QueueUserWorkItem(s => 
     { 
      var status = VendorLibrary.PerformLogin(); 
      callback.ReportLoginStatus(status); 
     }); 
    } 
} 

Dies ist eine einfache Art und Weise tun, die ThreadPool und anonyme Methode variable Capturing verwendet wird. Wenn Sie es mit einem freilaufenden Thread tun möchten, müssen Sie stattdessen ParameterizedThreadStart verwenden und den callback als Parameter übergeben.


Update für konkretes Beispiel:

Es scheint, dass hier was los ist, dass die IVendorService einige ereignisgesteuerte Modell für Rückrufe verwendet.

Da Sie InstanceContextMode.PerSession verwenden, können Sie den Rückruf tatsächlich nur in einem privaten Feld der Serviceklasse speichern und dann auf dieses Feld in Ihrem Event-Handler verweisen.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] 
public class VendorServer : IVendorServer 
{ 
    private IMyCallback callback; 
    private IVendorService vendorService; 

    public VendorServer() 
    { 
     callback = OperationContext.Current.GetCallbackChannel<IMyCallback>(); 
     vendorService = new VendorService(); 
     vendorService.AgentManager.AgentLoggedIn += AgentManager_AgentLoggedIn; 
    } 

    public void Login(string userName, string password, string stationId) 
    { 
     vendorService.Login(userName, password, stationId); 
    } 

    private void AgentManager_AgentLoggedIn(object sender, EventArgs e) 
    { 
     callback.ReportLoggedIn(...); 
    } 
} 

Wenn Sie sich entscheiden, später zu einem anderen Instanz-Modus zu wechseln, dann wird dies nicht funktionieren, weil jeder Kunde einen anderen Rückruf hat. Solange Sie im Sitzungsmodus bleiben, sollte dies in Ordnung sein.

+0

@Aaronaught, Ich habe den obigen Code zu meiner Instanz hinzugefügt. Irgendwelche Gedanken zur Umsetzung mit diesem Szenario? Insbesondere ist meine Login() -Methode in keiner Weise an den Rückruf gebunden. Das LoggedIn-Ereignis tritt ein, nachdem die Anmeldung initiiert wurde, wird aber manchmal nicht empfangen (im Falle eines Kommunikationsfehlers usw.). Aus diesem Grund zünde ich Ereignisse aus meiner Back-End-Bibliothek an, um anzugeben, wann sie empfangen werden. –

+0

Perfekt! Das Festlegen des Rückrufs als eine lokale Variable hat den Trick ausgeführt. Danke für Ihre Hilfe! –

0

Setzen Sie die betreffenden Ereignisse in eine thread-sichere (gesperrte) Warteschlange und lassen Sie den ausführenden Dienst (wie Sie es nennen) die Anzahl der Warteschlangen überprüfen. Je nach Bedarf aus der Warteschlange entfernen.

0

Du hast nicht IVendorServer präsentieren, aber nur für den Fall Sie nicht wissen, erfordert es das folgende Attribut:

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyCallback))] 

Auch Sie keinen Hintergrund-Thread müssen Ereignisse empfangen (I‘ Ich bin mir nicht einmal sicher, ob und wie es anwendbar ist. Sie müssen nur die Callback-API in der Klasse implementieren, die IMyCallback erweitert. Innerhalb dieser API wird die Antwort empfangen.

Sie könnten auch eine Sammlung von IMyCallback Instanzen der authentifizierten Clients sowie einen separaten Thread zum Ausführen von asynchronen Callbacks haben, aber das ist weit über den Rahmen Ihrer Frage hinaus und erfordert eine methodische Planung.

Verwandte Themen