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" />
<provider type=
"System.Runtime.Remoting.Caching.CachingServerSinkProvider, RemotingCache" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
<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("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";
}
}
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:
- The object Uri, typename and the method name.
- 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.
My name is Zhiheng Cao. I major in Eletronics Engineering, in University of Tokyo. I do part time jobs of computer programming and teaching high school students English and Mathematics.
CodeProject provided me with valuable information for my programming job many times in the past. I hope I can do my part by providing goodies I have.