Async WCF self hosted service

Moim celem jest zaimplementowanie asynchronicznej usługi hostowanej WCF, która uruchomi wszystkie żądania w jednym wątku i w pełni wykorzysta nowe funkcje asynchroniczne C # 5.

Moim serwerem będzie aplikacja konsoli, w której ustawięSingleThreadSynchronizationContext, jak określonotutaj, stwórz i otwórzServiceHost a następnie uruchomSynchronizationContext, więc wszystkie żądania WCF są obsługiwane w tym samym wątku.

Problem polega na tym, że chociaż serwer był w stanie pomyślnie obsłużyć wszystkie żądania w tym samym wątku,operacje asynchroniczne blokują wykonanie i są serializowane, zamiast przeplotu.

Przygotowałem uproszczoną próbkę, która odtwarza problem.

Oto moja umowa serwisowa (taka sama dla serwera i klienta):

[ServiceContract]
public interface IMessageService
{
    [OperationContract]
    Task<bool> Post(String message);
}

Implementacja usługi jest następująca (jest nieco uproszczona, ale ostateczna implementacja może uzyskać dostęp do baz danych lub nawet wywoływać inne usługi w sposób asynchroniczny):

public class MessageService : IMessageService
{
    public async Task<bool> Post(string message)
    {
        Console.WriteLine(string.Format("[Thread {0} start] {1}", Thread.CurrentThread.ManagedThreadId, message));

        await Task.Delay(5000);

        Console.WriteLine(string.Format("[Thread {0} end] {1}", Thread.CurrentThread.ManagedThreadId, message));

        return true;
    }
}

Usługa jest hostowana w aplikacji konsoli:

static void Main(string[] args)
{
    var syncCtx = new SingleThreadSynchronizationContext();
    SynchronizationContext.SetSynchronizationContext(syncCtx);

    using (ServiceHost serviceHost = new ServiceHost(typeof(MessageService)))
    {
        NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);

        serviceHost.AddServiceEndpoint(typeof(IMessageService), binding, address);
        serviceHost.Open();

        syncCtx.Run();

        serviceHost.Close();
    }
}

Jak widać, pierwszą rzeczą, jaką robię, jest ustawienie pojedynczego wątkuSynchronizationContext. Następnie tworzę, konfiguruję i otwieram ServiceHost. WedługTen artykuł, ponieważ ustawiłem SynchronizationContext przed jego utworzeniem,ServiceHost przechwyci to i wszystkie żądania klientów zostaną opublikowane wSynchronizationContext. W sekwencji zaczynamSingleThreadSynchronizationContext w tym samym wątku.

Stworzyłem klienta testowego, który zadzwoni na serwer w stylu fire-and-forget.

static void Main(string[] args)
{
    EndpointAddress ep = new EndpointAddress(address);
    NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
    IMessageService channel = ChannelFactory<IMessageService>.CreateChannel(binding, ep);

    using (channel as IDisposable)
    {
        while (true)
        {
            string message = Console.ReadLine();
            channel.Post(message);
        }
    }
}

Gdy wykonam przykład, otrzymuję następujące wyniki:

Klient

serwer

Wiadomości są wysyłane przez klienta z minimalnym interwałem (<1s). Spodziewałem się, że serwer otrzyma pierwsze połączenie i uruchomi je wSingleThreadSynchronizationContext (kolejkowanie nowegoWorkItem. Kiedyawait słowo kluczowe zostało osiągnięteSynchronizationContext zostanie ponownie schwytany, kontynuacja wysłana do niego, a metoda zwróci zadanie w tym momencie, uwalniającSynchronizationContext poradzić sobie z drugim żądaniem (przynajmniej zacząć się z tym uporać).

Jak widać po identyfikatorze wątku w dzienniku serwera, żądania są poprawnie wysyłane wSynchronizationContext. Jednak patrząc na znaczniki czasu, widzimy, że pierwsze żądanie jest zakończone przed rozpoczęciem drugiego, co całkowicie uniemożliwia cel posiadania serwera asynchronicznego.

Dlaczego tak się dzieje?

Jaki jest właściwy sposób implementacji serwera asynchronicznego hostowanego WCF?

Myślę, że problem dotyczy SingleThreadSynchronizationContext, ale nie widzę, jak zaimplementować go w inny sposób.

Zbadałem temat, ale nie mogłem znaleźć bardziej przydatnych informacji na temat asynchronicznego hostingu usług WCF, zwłaszcza przy użyciu wzorca opartego na zadaniu.

DODANIE

Oto moja implementacjaSingleThreadedSinchronizationContext. Jest zasadniczo taki sam jak ten wartykuł:

public sealed class SingleThreadSynchronizationContext  
        : SynchronizationContext
{
    private readonly BlockingCollection<WorkItem> queue = new BlockingCollection<WorkItem>();

    public override void Post(SendOrPostCallback d, object state)
    {
        this.queue.Add(new WorkItem(d, state));
    }

    public void Complete() { 
        this.queue.CompleteAdding(); 
    }

    public void Run(CancellationToken cancellation = default(CancellationToken))
    {
        WorkItem workItem;

        while (this.queue.TryTake(out workItem, Timeout.Infinite, cancellation))
            workItem.Action(workItem.State);
    }
}

public class WorkItem
{
    public SendOrPostCallback Action { get; set; }
    public object State { get; set; }

    public WorkItem(SendOrPostCallback action, object state)
    {
        this.Action = action;
        this.State = state;
    }
}

questionAnswers(1)

yourAnswerToTheQuestion