Skip to content

Long duration invocations

Alexey Yakovlev edited this page Apr 30, 2017 · 6 revisions

.NET Remoting — Long-duration invocations

Sometimes 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.

By Dmitry Belikov

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.

  1. Your server can meet ThreadPool limitations. In this case all .NET Remoting stuff and all things dependant on this will just stop working.
  2. You should apply a reasonable time-out on the client side.
  3. 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.
  4. A progress bar usually is not a problem at all. See this article for the sample. (dead link)

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 ManualResetEvent class to check for the timeout.

static void Main(string[] args)
{
   // Initiates the operation
   iInitiateLengthyOperation.StartOperation("Operation N" + 
         random.Next(100).ToString(), new Client());     

   // and wait until it finishes or time is up
   if (! InvocationCompletedEvent.WaitOne(5000, false))
     Console.WriteLine("Time is up.");
}   

// Is set when the invocation is completed.
public static ManualResetEvent InvocationCompletedEvent = 
          new ManualResetEvent(false);   

// Is called by an operation executer to transfer the operation 
// result to the client.
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
{
  // Starts a lengthy process in the separate background thread.
  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;

  // Simulates a long-duration calculations.
  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...");  

    // Warning: this call should be asyncrhonous. I can be 
    // lazy here only because I use Genuine Channels!
    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 ThreadPool-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.

// Summary description for IPerformLengthyOperation.
public interface IPerformLengthyOperation
{
   // Performs a lengthy operation.
   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.

/// Starts a lengthy operation.
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.

References

  1. The original article at CodeProject
  2. GenuineChannels NuGet package