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.
Setting ConcurrencyMode:=ConcurrencyMode.Multiple
solves the problem.
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>
Public Class GetDataService
[...]
End Class
ServiceBehaviorAttribute.ConcurrencyMode Property
the default is "Single"