Introduction
Sometimes the server needs to perform lengthy calculations in reply to the client�s request. If such calls are rare or do not take too much time, it�s not a problem. If your solution shows a tendency to have about 20-30 such invocations being performed concurrently at the same time, you should consider the following things. Any process that makes any invocation remotely should be counted as well, because execution time usually is noticeable and they should not hold on a Thread taken from the ThreadPool
.
- Your server can meet
ThreadPool
limitations. In this case all .NET Remoting stuff and all things dependant on this will just stop working.
- You should apply a reasonable time-out on the client side.
- Do not forget about sponsorship. Client should hold on the server business object as well as server may want to attach a sponsor to client�s object. Actually such things depend on design, so we will not consider them in this article.
- A progress bar usually is not a problem at all. See this article for the sample.
Generally our plans can look so: we initiate an action sending to the server our request. Server receives it and runs a lengthy process in the dedicated background thread to avoid and ThreadPool
-related problems. We will need to receive a result from that thread, so we provide our callback to receive a result.
Using the code (the first solution)
Let's study the first sample. The known layer contains only interfaces. Here follows a brief description of how to use the article or code - the class names, the methods and properties, any tricks or tips.
public interface IReceiveLengthyOperationResult
{
void ReceiveResult(string operationResult);
}
public interface IInitiateLengthyOperation
{
void StartOperation(string parameter,
IReceiveLengthyOperationResult resultReceiver);
}
Client initiates the operation execution and uses an instance of the "FONT-WEIGHT: 400">ManualResetEvent
class to check for the timeout.
static void Main(string[] args)
{
iInitiateLengthyOperation.StartOperation("Operation N" +
random.Next(100).ToString(), new Client());
if (! InvocationCompletedEvent.WaitOne(5000, false))
Console.WriteLine("Time is up.");
}
public static ManualResetEvent InvocationCompletedEvent =
new ManualResetEvent(false);
public void ReceiveResult(string operationResult)
{
Console.WriteLine("A result has been received from the server:
{0}.", operationResult);
InvocationCompletedEvent.Set();
}
Server implements lengthy operation provider that creates business objects run in the separate thread.
public class LengthyOperationImplementation
{
public LengthyOperationImplementation(string parameter,
IReceiveLengthyOperationResult resultReceiver)
{
this._parameter = parameter;
this._iReceiveLengthyOperationResult = resultReceiver;
Thread thread = new Thread(new ThreadStart(this.Process));
thread.IsBackground = true;
thread.Start();
}
private string _parameter;
private IReceiveLengthyOperationResult
_iReceiveLengthyOperationResult;
public void Process()
{
Console.WriteLine("We started long-duration calculations that
are expected to be finished in {0} milliseconds.",
CALL_DURATION_MS);
Thread.Sleep(CALL_DURATION_MS);
Console.WriteLine("Calculations are finished. Calling client
callback...");
this._iReceiveLengthyOperationResult.ReceiveResult
(this._parameter.ToUpper());
}
}
What we've got finally with this approach?
- We�ve run a request in the separate thread and avoided any Thread Pool-related problems. That�s good and we can do any lengthy processing there or invoke remote peers' objects.
- We have to write two interfaces for each type of long-duration invocations.
- In general we need to write a separate class and create its instance for each request.
- Client is able to keep track of timeout. We can use
ThreadPool.RegisterWaitForSingleObject
method to avoid holding on a thread on the client side.
Do you think it's a good idea to spend your time writing and supporting two interfaces and a class for each such invocation? No? Neither do I. We can get the same result by writing a few lines of code without any redundant interfaces or callbacks.
Using the code (the second solution)
Let's do the same with Genuine Channels now. And consider the difference!
Known layer contains the only one interface containing a declaration of the operation. It receives necessary parameters and return a result.
public interface IPerformLengthyOperation
{
string PerformOperation(string parameter);
}
Server just implements the operation and returns a result. Please notice you have the simplest code you can have here. You just perform an operation and return a result.
public string PerformOperation(string parameter)
{
Console.WriteLine("We started long-duration calculations that are
expected to be finished in {0} milliseconds.", CALL_DURATION_MS);
Thread.Sleep(CALL_DURATION_MS);
Console.WriteLine("Calculations are finished. Returning a
result...");
return parameter.ToUpper();
}
Client�s side as simple as it is possible. The secret is in Security Session parameters that force thread context and custom timeout for this call.
using(new SecurityContextKeeper(GlobalKeyStore.
DefaultCompressionLessContext + "?Thread&Timeout=5000"))
iPerformLengthyOperation.PerformOperation("Operation N" +
random.Next(100).ToString());
If you do not want to hold on client's thread, make an asynchronous call within the same Security Session parameters. Thread and timeout parameters will work for it as well.