Summary: This page contains information about common patterns frequently encountered with CCR.
Before reviewing this page please review the information on the CCR wiki:
http://channel9.msdn.com/wiki/default.aspx/Channel9.ConcurrencyRuntimeAn MSDN article on the implementation and design of the CCR, with code samples is available at:
http://msdn.microsoft.com/msdnmag/issues/06/09/ConcurrentAffairs/default.aspx
Causalities
Causalities are a generalization of try/catch, across threads, and also deal with joins, so they are
alot more powerfull and useful than single threaded try/catch. Causalities are logical context that flows with messages when a post occurs on a CCR port. When a handler runs because of a message that executed within a causality, this handler now also propagates the causality to any messages posted within its context.
The DSS runtime actually sets a causality each time your service handler rusn, so if any exectpion is thrown in your handler, or any async task you executed, BEFORE you post a response to the request, we will send a faul on your behalf! This is how causalities really make a huge difference with partial failure handling, they allow you at the right level,to deal with failure.
void Example()
{
// wait for failure on our exception port
Activate(dispatcherQueue, Arbiter.Receive(false, _exceptionTarget,
[ExceptionHandler));]
// start some task, asynchronously
[Activate(dispatcherQueue,Arbiter.FromHandler(RootCauseDepth0));]
// start some other task, asynchronously
// this task actually posts to a port that causes another task to execute
// but even if an exception is thrown in that child task, causalities still
// propagate the error to the right exception port
[Activate(dispatcherQueue,Arbiter.FromHandler(RootCauseDepth0));]
// note that if any exception is thrown on our task or any tasks it caused
//to run,our Exception handler will execute
}
void [RootCauseDepth0()]
{
[Dispatcher.AddCausality(new] Causality("test", exceptionTarget));
throw new [InvalidOperationException(]
"Throwing exception to test causality support");
}
void [RootCauseDepth1()]
{
[Dispatcher.AddCausality(new] Causality("test", _exceptionTarget));
// we are running within a causality. Any exceptin we throw here or in any
// continuation that was spawned
// from actions started here, should be captured and posted on the
// exception port associated with the
// causality
Port<bool> nextStep = new Port<bool>();
nextStep.Post(true);
Activate(dispatcherQueue, Arbiter.Receive(false, nextStep, delegate(bool b)
{
throw new [InvalidOperationException(]
"Throwing exception to test causality support");
}));
}
}
How Not to Starve
Starvation is a common problem in multi-threaded application. Starvation happens when one or more threads keep grabbing resources without releasing them. The other threads of the application cannot continue because of the lack of resources. This leads to unresponsive system that users might perceive as slow.
Starvation can occur in systems that use threadpools for event handling, i.e. almost every message-driven application. Messages compete for a limited number of resources--threads. If an event handler takes a very long time (for example by performing blocking I/O on slow, medium or a long running computation) the thread is blocked and cannot be used for other events. The threadpool will eventually run dry and unhandled messages starve.
Windows Forms applications are an extreme example: the threadpool has exactly one thread. The first event handler that blocks, blocks the entire application. Such bugs are usually easy to track down. However, when the threadpool size is greater than one, the symptoms are less obvious: The performance gradually degrades because every blocking thread decreases the number of available threads that can handle messages. Eventually the threadpool is empty and the application blocks.
Example - @System.Threading.Timer@ Many embedded controllers require periodic polling; for example, to query the sensor state or to refresh output values. Such behavior is commonly implemented using timers. After each tick, you can access a device specific API or communicate with the device directly.
The class @
System.Threading.Timer@ is a convenient way to shoot yourself in the foot. All instances of this class share the same threadpool (which is usually sized to @50@). This means that a single misbehaved activity affects the entire application.
If the timer interval is set to @50 ms@, but each event handler (timer tick) takes @100 ms@ to complete; for each handled event, two unhandled messages pile up. This means that the system can
never recover.
How Can I Avoid Starvation?
There are a few simple thing that you can do to avoid starvation.
- Avoid using blocking I/O in an event handler.
- Split individual, long-running computations into several shorter ones.
- Choose useful intervals for timer-driven behavior.
- Serialize timer events of the same activity.
MSRS DSS and CCR assist you in writing applications that meet these guidelines.
Avoid Blocking I/O
Blocking I/O occurs when ever you perform a read (or write) operation, for example on a file or serial port. If less data than requested is available, the thread blocks I/O until the data arrives. In many situation this time can be rather long.
Instead of synchronous I/O, you should use asynchronous I/O. For example, the class @
System.IO.SerialPort@ provides a
@DataReceived@ event, which informs you about available data. In the event handler, you can consume the available data. If enough data has been received, you can post a message to your service and handle it accordingly.
See also
@samples/Platforms/MobileRobots/Arcos@ and @samples/Platforms/Traxster@ in the CTP release.
Split Long-Running Computations
Some things simply take time. However, long tasks shouldn't block other tasks. A simple solution is to split long tasks up into smaller, quicker tasks. Splitting can be done in many ways; for instance, you can stage your processing. After each stage, post a message to your service that indicates it's safe to begin the next stage. The handler for that message executes that stage. An even simpler way is using the @yield return@ keyword:
[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public [IEnumerator<ITask>] [LongRunningHandler(DoSomething] message)
{
for (int i=0; i<4096; i++)
{
helperPort.Post(new [WorkItem(data,response));]
yield return Arbiter.Receive(false,response,
delegate()
{
// received result for sub task }
);
}
yield break;
}
We only recommend the above pattern when you can get benefits from computing results in parallel. Otherwise, blocking a thread, even in the context of the CCR is fine, if you get your own private dispatcher. In the DSS environment, having your service gets its own "mini threadpool" is trivial:
Add the following attribute on the service class:
ActivationSettings(ShareDispatcher = false)public class
MyService :
DsspServiceBase{
}
Serialize Timer Events
In many cases, it doesn't make sense to access the same device from two different timer events in parallel. For example, it might not make sense to send a new query command to a sensor device if the previous command hasn't completed yet. Here, timer events should be serialized, i.e. the next timer event should only fire after the previous event has been handled.
[Port<DateTime>] _timerPort = new [Port<DateTime>();]
protected override void Start()
{
...
_timerPort.Post(DateTime.Now); // start
Activate(Arbiter.Receive(true, _timerPort,
TimerHandler));
}
void [TimerHandler(DateTime] signal)
{
// perform timer triggered action here
...
// reactivate the timer
Activate(
Arbiter.Receive(
false, // fires once
TimeoutPort(1000), delegate(DateTime time)
{
_timerPort.Post(time);
}
);
);
}
Using Third Party APIs
Many
APIs for controller devices assume the burden of communication with the device (so you don't have to) and provide a nice, high-level abstractions. However, these
APIs may not follow the guidelines listed above. For example, they might use synchronous I/O. In this case, you have to make sure that they don't interfere with other parts of the application and vice versa. In many situation, it is sufficient to move the communication with the API to a dedicated thread. From this thread, you can post messages to services if something interesting happens. Remember, CCR is complete thread safe. You can post messages to any port from any thread. Your thread could look like this:
while (_running)
{
controller.SendQery(); // synchronous I/O
SensorData data =
controller.WaitForData(); // tell your service about the new data
_mainPort.Post(new
SensorUpdate(data)); }