Our Blog

.Net remoting, interesting and frustrating

by BillyTheKid July 08, 2009 09:33

Well I've been testing out .Net remoting and so far it is pretty frustrating.  There is very little good examples anywhere on the net, the error messages it generates are cryptic, and I think there are a few known framework bugs that are pretty easy to run into.  Basically what I'm trying to build is a service on a machine that executes long running processes in a queue, specifically ffmpeg, mencoder, possibly flvtool2 or others.

My project has 3 parts, server, client, and shared interfaces library.  There are two interfaces defined, one for the task service queue, the other a custom task object, and for the purposes of this example, I've kept only the taskId field on the object.

Initially I had exposed the custom task object using RegisterWellKnownServiceType and although I could add multiple tasks to the queue, they all seemed to be shallow copies of the same object, changing a property on one changed them all.  I then decided to go with the factory pattern and added a CreateTask method to my task service which returned a full value object copy of an ITask.

One thing to note, when passing a complex object back and forth using remoting, you have to set the TypeFilterLevel to Full or you will get serialization errors.  The complex object needs to be marked as [Serializable] and should NOT inherit from MarshalByRefObject, whereas the main task service absolutely must. 

Here is the code for my shared interfaces file, I.cs

using System;

namespace Technoponics.Video {

public interface ITaskService {
string GetStatus();
ITask CreateTask();
void AddTask(ITask t);
}

public interface ITask {
string taskId{get;set;}
}

}

For the server component, here is the code, S.cs

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using Technoponics.Video;

namespace TPN {

class Console_Test {

static void Main(string[] args) {

BinaryClientFormatterSinkProvider clientProvider = null;
BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
serverProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;

System.Collections.IDictionary props = new System.Collections.Hashtable();
props["name"] = "Server";
props["port"] = 7099;
props["typeFilterLevel"] = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;

TcpChannel channel = new TcpChannel(props,clientProvider,serverProvider);

Technoponics.Video.TaskService ts = new Technoponics.Video.TaskService();
RemotingServices.Marshal(ts,"Billy_1");

Console.WriteLine("server started");
Console.WriteLine("Press the enter key to exit...");
Console.ReadLine();

}
}
}

namespace Technoponics.Video {

public class TaskService : MarshalByRefObject, ITaskService {

private System.Collections.Generic.Queue<BillyTask> _q = new System.Collections.Generic.Queue<BillyTask>();

public TaskService() {}

public override object InitializeLifetimeService() {return null;}

public string GetStatus() {
return "Ok - "+_q.Count.ToString()+" items queued";
}

public ITask CreateTask() {
return new BillyTask();
}

public void AddTask(ITask t) {
_q.Enqueue((BillyTask)t);
Console.WriteLine("AddTask "+t.taskId+" first queued "+_q.Peek().taskId+" queue count "+_q.Count.ToString());
}

}

[Serializable]
public class BillyTask : ITask {

public BillyTask() {}

private string _taskId;

public string taskId {
get{return _taskId;}
set{_taskId=value;}
}

}

}


Then the client code, C.cs

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using Technoponics.Video;

namespace TPN {

class Console_Test {

public delegate void AddTaskDelegate(ITask ct);

static void Main(string[] args) {

ChannelServices.RegisterChannel(new TcpChannel(0),false);

ITaskService ts = (ITaskService) Activator.GetObject(
typeof(ITaskService),
"tcp://localhost:7099/Billy_1" );

if (args.Length==0) Console.WriteLine(ts.GetStatus());

if (args.Length==1) {
ITask t = ts.CreateTask();
t.taskId=args[0].ToString();
new AddTaskDelegate(ts.AddTask).BeginInvoke(t, null, null);
}

}

}

}

One thing to note is that rather than calling taskService.AddTask directly I am using a delegate to avoid the client having to wait for ffmpeg to exit before it continues.

You can compile the code as follows:

csc /target:library I.cs

csc /target:exe S.cs /r:I.dll

csc /target:exe C.cs /r:I.dll

In the server component if you use RegisterWellKnownServiceType, the object will not be created until the first client calls it.  For my processing queue, I actually want to have it active and processing should I happen to restart the service, which is why I used RemotingServices.Marshall.

Now here is my biggest problem everything works perfectly until I move the client executable to a different directory from the server module.  If I run the client without parameters, it calls the GetStatus() on the server which does work fine because it returns a simple type, but when I try to get it to add an ITask object to the queue, it give the following error:

System.Runtime.Serialization.SerializationException: Unable to find assembly 'S, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'

This is the one I have yet to work around, and I think it is a known framework bug I am running into.

First thing, it can’t find the assembly called “S” the server assembly, which the client does not, or at least should not need.  The client should only need the shared interfaces library, I.dll. 

It works fine with simple types, so I think my solution will be to get rid of the custom task object and just create an addTask function that accepts the parameters I wish to send as simple types rather than passing a complex task object. 

Comments

07/23/2010 01:41:35 #

Alcohol is the anesthesia by which we endure the operation of life.

no fax payday loans United States

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading