Search code examples
vb.netmultithreadingwcfasynchronoustask-parallel-library

Calls to async WCF Service executed sequentially


I have built a basic WCF console server application. My goal is to handle multiple calls to it in parallel fashion but the current version handles them sequentially.

Please bear with me, as there is a wall of code to follow, but it's pretty basic stuff. Just hard for me to isolate as it is part of a fairly large VS Solution.

I've gone down the road of TPL and async/await keyword which I basically understand and like.

The service interface:

<ServiceContract()>
Public Interface IGetBackendData

    <OperationContract>
    Function SendRequest(request As Request) As Task(Of RequestResponse)

    <OperationContract>
    Function GetNextPackage(serverJobID As Guid) As Task(Of PackageBase)

End Interface

The proxy:

Public Class imBackendServerProxy
    Inherits ClientBase(Of IGetBackendData)
    Implements IGetBackendData

    Public Function SendRequest(request As Request) As Task(Of RequestResponse) Implements IGetBackendData.SendRequest
        Return Channel.SendRequest(request)
    End Function

    Public Function GetNextPackage(serverJobID As Guid) As Task(Of PackageBase) Implements IGetBackendData.GetNextPackage
        Return Channel.GetNextPackage(serverJobID)
    End Function
End Class

And the implementation:

Public Class GetDataService
    Implements IGetBackendData

    Private ActiveJobs As New Dictionary(Of Guid, ServiceJobBase)

    Private Function ProcessRequest(request As Request) As RequestResponse
        Dim newJob As ServiceJobBase

        Select Case request.Command.CommandType
            Case ImagiroQueryLanguage.CommandTypes.CommandHello
                newJob = New HelloJob
            Case Else
                Throw New ArgumentException("Do not know how to process request")
        End Select

        If newJob IsNot Nothing Then
            newJob.AssignedRequest = request
            ActiveJobs.Add(newJob.ID, newJob)
            Return newJob.GetResponse()
        End If

        Throw New ArgumentException("job could not be started")
    End Function

    Public Async Function SendRequest(request As Request) As Task(Of RequestResponse) Implements IGetBackendData.SendRequest
        Console.WriteLine("Request recieved")
        Dim mytask As Task(Of RequestResponse) = Task.Factory.StartNew(Function() ProcessRequest(request))
        Await Task.Delay(1500)
        Return Await mytask.ConfigureAwait(True)

    End Function


    Private Function GenerateNextPackage(jobid As Guid) As PackageBase
        If Not ActiveJobs.ContainsKey(jobid) Then
            Throw New ArgumentException("job could Not be found")
        End If

        Dim nextPackage As PackageBase = ActiveJobs(jobid).GetNextPackage()
        If TypeOf (nextPackage) Is PackageEnd Then
            ActiveJobs.Remove(jobid)
        End If
        Return nextPackage
    End Function

    Public Async Function GetNextPackage(serverTaskID As Guid) As Task(Of PackageBase) Implements IGetBackendData.GetNextPackage
        Dim mytask As Task(Of PackageBase) = Task.Factory.StartNew(Of PackageBase)(Function() GenerateNextPackage(serverTaskID))
        Await Task.Delay(1500)
        Return Await mytask.ConfigureAwait(True)
    End Function
End Class

A "Request" object contains a "Command" object (derived from CommandBase) plus additional information. A "Package" object (derived from PackageBase) contains the data to be transmitted from Server to Client.

The basic idea of how the communication is supposed to work is like this:

1. "Request" phase
Client --Request--> Server
Client <--GUID A -- Server 

2. "Data" phase
Client --  GUID A  --> Server
Client <--DataOrStop-- Server

3. Repeat step 2. until Server says stop.

To consume the data and request response, I have the following class:

Public Class DataReceiver
    Public Event DataPackageRecieved(sender As Object, arg As DataPackageReceivedEventArgs)
    Public Event EndOfTransmission(sender As Object, arg As EventArgs)

    Public Sub New(response As RequestResponse, proxy As imBackendServerProxy, dataRecieved As DataPackageRecievedEventHandler, endOfTransmission As EndOfTransmissionEventHandler)
        ID = response.JobID
        p = proxy

        AddHandler Me.DataPackageRecieved, dataRecieved
        AddHandler Me.EndOfTransmission, endOfTransmission

        FetchData()
    End Sub

    Public Property ID As Guid
    Private p As imBackendServerProxy

    Private Sub FetchData()
        Dim t As Task(Of PackageBase) = Task.Factory.StartNew(Of PackageBase)(Function() p.GetNextPackage(ID).Result)
        Debug.Print("Waiting for Result FetchData")
        t.ContinueWith(AddressOf DoneFetching)
    End Sub

    Public Delegate Sub ProcessDataPackageDelegate(recievedDataPackage As PackageBase)

    Public Property ProcessDataPackage As ProcessDataPackageDelegate

    Private Sub DoneFetching(arg As Task(Of PackageBase))
        If arg.IsCompleted Then
            If TypeOf (arg.Result) Is PackageEnd Then
                RaiseEvent EndOfTransmission(Me, Nothing)
            Else
                RaiseEvent DataPackageRecieved(Me, New DataPackageReceivedEventArgs With {.DataPackage = arg.Result})
                FetchData()
            End If
        End If
    End Sub

End Class

In my WPF test client application I have a Button, with which I can send Requests to the server. The HelloCommand (derived from CommandBase) class is used to transport an integer "n" to the server. The server then responds to each of the following n GetNextPackage calls with a HelloPackage (derived from PackageBase) and finally with a EndPackage (derived from PackageBase).

The logic for this is handled in the ServiceJob objects (derived from ServiceJobBase) - basically every "Command" Object has a corresponding "ServiceJob" object which in turn sends corresponding "Package" objects to the sequential client requests.

As the client handles the needed "sequentiality" of the "data requests" i.e. sequential calls to the GetNextPackage function, those calls will never overlap. But I would very much like two or more seperate sequences of calls to GetNextPackage - and their respective "ServiceJobs" - to be executed in parallel on the server. And that's not happening.

Adding a simple counter to the HelloServiceJob class to easily identify each request, one press on the button in my WPF client yields the following output on the server, while the UI stays responsive.

Request recieved (0)
Sending HelloPackage - 6 remaining (0)
Sending HelloPackage - 5 remaining (0)
Sending HelloPackage - 4 remaining (0)
Sending HelloPackage - 3 remaining (0)
Sending HelloPackage - 2 remaining (0)
Sending HelloPackage - 1 remaining (0)
Sending HelloPackage - 0 remaining (0)
Sending no more HelloPackages

with 1.5 seconds between each line as expected.

Three quick consecutive presses yields the following output on the server, while the UI stays responsive.

Request recieved (1)
Request recieved (2)
Request recieved (3)
Sending HelloPackage - 6 remaining (1)
Sending HelloPackage - 6 remaining (2)
Sending HelloPackage - 6 remaining (3)
Sending HelloPackage - 5 remaining (1)
Sending HelloPackage - 5 remaining (2)
Sending HelloPackage - 5 remaining (3)
Sending HelloPackage - 4 remaining (1)
Sending HelloPackage - 4 remaining (2)
Sending HelloPackage - 4 remaining (3)
Sending HelloPackage - 3 remaining (1)
Sending HelloPackage - 3 remaining (2)
Sending HelloPackage - 3 remaining (3)
Sending HelloPackage - 2 remaining (1)
Sending HelloPackage - 2 remaining (2)
Sending HelloPackage - 2 remaining (3)
Sending HelloPackage - 1 remaining (1)
Sending HelloPackage - 1 remaining (2)
Sending HelloPackage - 1 remaining (3)
Sending HelloPackage - 0 remaining (1)
Sending HelloPackage - 0 remaining (2)
Sending HelloPackage - 0 remaining
Sending no more HelloPackages (1)
Sending no more HelloPackages (2)
Sending no more HelloPackages (3)

While the order is expected, each line takes 1.5 seconds to execute, the client and server only ever exchange on message at a time.

After reading a lot of articles, I'm more confused than anything. I cannot pinpoint what I need to do to make the three "Jobs" execute in parallel, not even whether this is the totally wrong way to go about this or if it's a simple configuration error.

I am running server and client on the same machine, using netTcpBinding which if i understand it correctly is needed and suitable for multiple parallel requests between client and server.

I have read and (hopefully) understood the following article, but I do not see how this translates to my case:tasks are still not threads and async is not parallel

How can I make the Jobs that are answering the calls run on different threads then? I am fully aware that the Return Await statement just waits for the execution to finish, but this is not the problem. What I want is three of those statements waiting to finish in parallel but the pipeline between the server and client seems to only hold one message at a time?

Thank you all for you time and input, I really appreciate it.


Solution

  • Setting ConcurrencyMode:=ConcurrencyMode.Multiple solves the problem.

    <ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>
    Public Class GetDataService
        [...]
    End Class
    

    Service​Behavior​Attribute.​Concurrency​Mode Property

    the default is "Single"