65.9K
CodeProject is changing. Read more.
Home

Practical .NET Remoting Caching Sink

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (11 votes)

Jun 28, 2003

2 min read

viewsIcon

60853

downloadIcon

511

A cache mechanism for .NET Remoting

Introduction

.NET Remoting solutions can be slow because of unnecessary "chatty" calls. For programmers, making a method call is easier than creating sockets, preparing buffers, calling send() and recv(), and then parsing the result. So much easier that he often forgets about performance. Another reason is that when we want to use PropertyGrid to present a remoted (MBR) object: A lot of property get/set methods are called, some of them are unnecessary, but we can't help because we can't modify code inside PropertyGrid, yet we want to use this powerful control.

By using this caching sink, the task of optimizing performance of Remoting projects can be greatly simplified to just placing some attributes. The caching sink works on the client side by returning previous result if the server indicated the client can do so. Methods can have "caching attributes", indicating how long clients can cache the result.

How to use

Configuration Files

Here is the remoting config file that shows where to insert these sinks.

<serverProviders>
  <formatter ref="binary" />
  <!-- Sets X-Cache from CacheTime attribute of the method -->
  <provider type=
    "System.Runtime.Remoting.Caching.CachingServerSinkProvider, RemotingCache" />
</serverProviders>
<clientProviders>
  <formatter ref="binary" />
  <!-- return cached results if applicable -->      
  <provider type=
    "System.Runtime.Remoting.Caching.CachingClientSinkProvider, RemotingCache" />
</clientProviders>

Of course, you need to compile the source code and make RemotingCache.dll.

Caching Attributes

using System.Runtime.Remoting.Caching;
//CacheTime
[CacheTime("00:01:00")] 
public override string ToString() {
  return "some string that won't change for one minute";
}
public string Name { 
[CacheTime("01:00:00")] 
get{
    return "some property that won't change for one hour";
  }
} 

//InfinitelyCached
public string Name { 
[InfinitelyCached] 
get{
   return "some property that won't change forever";
  }
}

How it works

The sink on the server side is easy: it simply reads the caching attributes on the method being called and return the information in form of "X-Cache" entry in the response header. the string in this entry will be passed to TimeSpan.Parse() on the client side (so the format("hh:mm:ss") should be used in the CacheTime attribute).

The most difficult part in implementing this caching mechanism is on the client side. It is the part of designing a hash-key scheme so that only the exact same call matches the entry, where the previous result is stored. I'll briefly explain the design taken in this work. The hash-key has two part:

  1. The object Uri, typename and the method name.
  2. The request stream.( Note because the sink is placed after formatter, all information in IMessage is contained in Request stream, so IMessage can totally be ignored )

When the two hash keys are compared, the first part is compared first. So when the call is on a different object, or it has a different method name, the comparison will end without comparing the whole data in request (e.g. parameters).

For hash-key generation, there are other approaches that use BinaryFormatter to serialize the request IMessage, and store the serialized "opaque" data as key. This proves not only to be time consuming (thus defying the purpose of performance improvement by caching), but also extremely unstable -- The binary formatter seems to be unable to serialize IMessage: it throws ExecutionEngineException which is un-catchable and brings the whole execution environment down.

By placing the sink AFTER formatter in the client sink provider chain, My solution does not need to do serialization to calculate the key.