Creating an Actor
The term 'Actor' is used here for services that are not only passively waiting for a request but also may actively post requests to other services.
An actor may be constructed by using one or more objects of class ActorInput and ActorOutput.
An ActorOutput may be linked to a single ActorInput while an ActorInput supports many ActorOutputs connected to it. This is the classic client-server communication pattern provided by the TCP subsystem.
You may use several ActorOutput objects when needing more output links or several ActorInputs when needing different input link types.
A user provided class represents the main actor class. It may contain many private members not accessible from outside. Only the constructor, a start method and the IActorInput/IActorOutput interfaces form the public, threadsafe interface to the actor. All private member objects including the ActorInputs and ActorOutputs are internally accessed from one dedicated thread, the actor thread only.
Example: Test1.ServiceActive.cs. Input1,2 and Output1,2 are objects of type IActorInput and IActorOutput. They represent linkable communication endpoints or 'I/O ports' of the actor.
Linking an Actor
When calling the constructor of an actor class, the internal structure is created. But the actor itself should not link its ActorOutputs to remote IActorInputs (or vice versa).
This step should be left to higher level management code at a later initialization stage.
Thereby your actor may be used for application internal or networked scenarios.
The higher level management code must call one of the following methods to establish a link on each IActorOutput and IActorInput interface exposed by the actors:
- LinkOutputTo: Establishes an application internal link.
- Outgoing messages are passed by references without serialization/deserialization.
- LinkOutputToRemoteService: Creates a WcfBasicClientAsyncAwait proxy to connect a remote WCF service.
- Outgoing messages are serialized using the settings from 'WcfDefault.ClientConfiguration'.
- LinkInputToNetwork: Creates a WcfServiceAssistant hosting a WCF service.
- Incoming messages are deserialized using the settings from 'WcfDefault.ServiceConfiguration'.
- The service name must be unique in the plant. It is published to the WcfRouter application running on the local host.
Starting an Actor
Creating and linking are operations that are executed on external thread(s). The internal thread is not running yet, the actor is not active.
The actor thread may be created and started outside or inside the actor. Anyway you need a method e.g. 'start' that is running on the actor thread and is bringing the actor to life.
One of the first methods the start method must call (on the actor thread!) are 'Open' on its ActorInput and 'TryConnect' on its ActorOutput objects.
This is important because the thread sychronization context is picked up when Open or TryConnect is called for the first time.
Later on, all incoming requests and responses are executed on this single thread. No further care for threadsafety is needed.
When the ActorPort.IsMultithreaded property is set, no synchronization context is picked up, messages are passed to the input handlers without using the message queue and without changing the synchronization context. The programmer is responsible to take care for all multithreading issues in this use case.
Accessing an Actor
From the outside you may send messages to each public accessible IActorInput interface.
The outgoing and incoming links to an actor use asynchronious messaging with messagequeues and are threadsafe.
IActorInput contains all functionality for incoming connections.
These include the 'PostInput' and 'PostInputFrom' methods.
When you just call PostInput, you are an anonymous partner and cannot get a response message to your request.
To receive responses, create an ActorOutput object representing a new output pin. Then link the ActorOutput to the input you want to call.
To send a message now, you have to call SendOut or SendReceiveAsync on your own ActorOutput object.
Three possibilities exist to receive the response:
- Await the response of the awaitable method 'SendReceiveAsync' inside a C# async method (needs .NET 4.5).
- Pass a AsyncResponseHandler method or a lambda expression to 'SendOut'.
- Handle the response in the default WcfMessageHandler method passed to the constructor of ActorOutput.
Assemblies exposing input- and output interfaces for later linking
Actors may be designed to be used as library components and be distributed in an assembly.
The assembly exposes IActorInput and IActorOutput interfaces to the user. The assembly user may link these interfaces to internal or external actors.
Not much effort by you (the assembly creator) is needed to allow its inputs and outputs beeing used in the same application, on another thread or in another application on a remote host.
You should refrain from shared data between actors inside the same application. Messages should not pass references to mutable objects as long as these objects may be accessed from different actors. When sending simple, immutable messages or copies of data, nobody has to care about threading problems like race conditions and deadlocks.
You should refactor your messages with care when sending them to remote applications.
Think always about old, running applications receving the new message.
ActorInput, ActorOutput class diagram
Generic ActorInput<TSC> and ActorOutput<TOC> class
You may create your active service using ActorInput<TSC> objects instead of plain ActorInput.
For each connecting client AsyncWcfLib creates a new object of your given type TSC. The sender context (TSC) object is passed to the WcfMessageHandler<TSC> method on each request. This allows you to store individual data for each connected client. This data is kept in memory during the entire time the actor is running. When using the right service- and client identification, TSC data is retained even after reconnected network connections and ending WCF sessions.
There exists one situation, when TSC data is deleted on an actor:
The client must run in an application of 'temporary host session' mode (WcfDefault.IsProcessIdUsed == true). And the connection is disconnected or has timed out. In this situation the TSC data is deleted because a newly started application (with same name) is not guaranteed to get a different process id.
You also may create and store a output context (TOC) object in each ActorOutput<TOC> instance.
Keep data in the TOC relating to the service connected to this output.
TSC and TOC data is not sent to the linked input/output like a message.