2010-04-08 15 views
8

Oto moja sytuacja. Napisałem usługę WCF, która wywołuje jedną z baz kodu dostawcy naszego dostawcy, aby wykonywać operacje, takie jak logowanie, wylogowanie itp. Warunkiem tej operacji jest posiadanie wątku tła w celu odbierania zdarzeń w wyniku tej akcji. Na przykład akcja logowania jest wysyłana w głównym wątku. Następnie, w wyniku logowania, zostaje odebranych kilka zdarzeń z usługi dostawcy. Może być odebranych 1, 2 lub kilka zdarzeń. Wątek tła działający na czasomierzu odbiera te zdarzenia i uruchamia zdarzenie w usłudze wcf, powiadamiając o nadejściu nowego zdarzenia.Usługa WCF z wywołaniami zwrotnymi pochodzącymi z wątku tła?

Zaimplementowałem usługę WCF w trybie dupleksu i zaplanowałem używanie wywołań zwrotnych do powiadamiania interfejsu użytkownika o nadchodzących zdarzeniach. Oto moje pytanie: Jak wysłać nowe zdarzenia z wątku tła do wątku, który wykonuje usługę?

Teraz, gdy zadzwonię pod numer OperationContext.Current.GetCallbackChannel<IMyCallback>(), operacja Kontekst jest pusta. Czy istnieje standardowy wzorzec do obejścia tego?

Używam PerSession jako mój SessionMode na ServiceContract.

AKTUALIZACJA: Pomyślałem, że przedstawię dokładniejszy scenariusz, demonstrując, w jaki sposób odbieram zdarzenia z kodu dostawcy. Moja biblioteka odbiera każde zdarzenie, określa, czym jest zdarzenie, i odpala zdarzenie związane z tym konkretnym wydarzeniem.

Mam inny projekt, który jest biblioteką klasy specjalnie do połączenia z usługą dostawcy. wyślę całą realizację usługi w celu uzyskania jaśniejszego obrazu:

[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 
          }; 
    } 
} 

Przedmiotem AgentEvent zawiera zwrotnego jako jednego z jego właściwości, a ja myślałem, że ja wykonać zwrotnego tak:

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

AgentEvent to obiekt zdefiniowany w serwisie:

[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 wygląda następująco:

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

Callback jest zaimplementowany na kliencie i wykorzystuje porporty EventArgs w AgentEvent do zapełnienia danych w interfejsie użytkownika. Jak przekazać wystąpienie OperationContext.Current z wątku głównego do wątku tła?

+0

W aktualizacji brakuje wystarczających informacji, aby naprawdę zrozumieć, co się dzieje. Gdzie znajduje się ten kod? Jak wykonać "logowanie"? Czy jest to jakaś metoda asynchroniczna? Co się dzieje z tym "AgentEvent" (gdzie właściwie jest używany)? A jaki jest typ 'agentEvent.Callback' i jak/kiedy jest używana jego wartość? – Aaronaught

+0

Dodałem pełniejszy przykład tego, co próbuję zrobić. Wydarzenia, które otrzymuję, są uruchamiane z biblioteki, której używam w ramach usługi, dlatego muszę je zasubskrybować. –

Odpowiedz

5

OperationContext.Current jest dostępna tylko w wątku, który faktycznie wykonuje operację. Jeśli chcesz, aby był dostępny dla wątku roboczego, musisz przekazać odwołanie do kanału zwrotnego do tego wątku.

więc operacja może wyglądać niejasno jak:

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

Jest to prosty sposób to zrobić przy użyciu ThreadPool i anonimowe metody zmiennej przechwytywanie. Jeśli chcesz zrobić to z wolnym wątkiem, musisz zamiast tego użyć parametru ParameterizedThreadStart i przekazać jako parametr parametr callback.


Aktualizacja dla konkretnego przykładu:

Wydaje się, że to co się dzieje jest to, że IVendorService wykorzystuje pewne modelu zdarzeniami dla wywołania zwrotne.

Ponieważ używasz InstanceContextMode.PerSession, możesz po prostu zapisać wywołanie zwrotne w prywatnym polu samej klasy usługi, a następnie odwołać się do tego pola w procedurze obsługi zdarzeń.

[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(...); 
    } 
} 

Jeśli zdecydujesz się przełączyć na inny tryb instancji później na to nie będzie działać, ponieważ każdy klient będzie miał inny zwrotnego. Tak długo, jak utrzymujesz go w trybie sesji, powinno to być w porządku.

+0

@Aaronaught, dodałem kod powyżej konkretny dla mojego wystąpienia. Wszelkie przemyślenia na temat realizacji tego scenariusza? W szczególności moja metoda Login() nie jest w żaden sposób powiązana z wywołaniem zwrotnym. Zdarzenie LoggedIn przychodzi po zainicjowaniu logowania, ale czasami nie jest odbierane (w przypadku błędu komunikacji itp.). Z tego powodu uruchamiam zdarzenia z mojej biblioteki zaplecza, aby wskazać, kiedy są odbierane. –

+0

Idealny! Ustawienie wywołania zwrotnego jako zmiennej lokalnej wykonało tę sztuczkę. Dzięki za pomoc! –

0

Połóż zdarzenia w kolejce z zabezpieczeniem wątków (zablokowane) i uruchom usługę wykonującą (tak ją nazywasz), sprawdzając liczbę kolejki. Usunąć w razie potrzeby.

0

Nie prezentujemy IVendorServer, ale tylko w przypadku, gdy nie wiem, to wymaga następujący atrybut:

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

Również nie potrzebujesz wątek tła do odbierania zdarzeń (I” nie wiem nawet, czy i jak ma to zastosowanie). Wystarczy zastosować API wywołania zwrotnego w klasie, która rozszerza się o IMyCallback. W tym interfejsie API jest miejsce, w którym otrzymano odpowiedź.

Można również uzyskać kolekcję instancji IMyCallback uwierzytelnionych klientów oraz osobny wątek do wykonywania asynchronicznych wywołań zwrotnych, ale to wykracza poza zakres pytania i jest czymś, co wymaga planowania metodycznego.

Powiązane problemy