Click here to Skip to main content
15,896,338 members
Articles / General Programming / Threads

Declarative multithreading

Rate me:
Please Sign up or sign in to vote.
4.94/5 (39 votes)
13 Mar 2012CDDL19 min read 59.3K   862   139  
An introduction and proof of concept code for the idea of declarative multi threading in C#.
using System;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting;
using System.Reflection;

namespace ThreadBound
{
    /// <summary>
    /// RealProxy that is used by the ThreadBoundAttribute. 
    /// This class is where the "magic" of transporting method calls to the correct
    /// thread is implemented.
    /// </summary>
    internal class ThreadBoundProxy : RealProxy
    {
        /// <summary>
        /// Reference to the real object instance that this proxy represents.
        /// </summary>
        private MarshalByRefObject RealObject;

        /// <summary>
        /// The SynchronizationContext that is used to execute all method calls that are
        /// not marked as FreeThreaded.
        /// </summary>
        private SynchronizationContext ExecutionContext;

        /// <summary>
        /// c'tor: Used to bind an object that was not yet created.
        /// </summary>
        /// <param name="newType">The type of the object represented by this proxy.</param>
        /// <param name="executionContext">The execution context that should be used for executing all methods not marked with the FreeThreaded attribute.</param>
        public ThreadBoundProxy(Type newType, SynchronizationContext executionContext) :
            base(newType)
        {
            ExecutionContext = executionContext;
        }

        /// <summary>
        /// c'tor: Used to bind an object that was already created.
        /// </summary>
        /// <param name="newType">The type of the object represented by this proxy. This must be the type of the instanfce referenced by realObject.</param>
        /// <param name="realObject">Reference to the already created instance that gets all of the method calls.</param>
        /// <param name="executionContext">The execution context that should be used for executing all methods not marked with the FreeThreaded attribute.</param>
        public ThreadBoundProxy(Type newType, MarshalByRefObject realObject, SynchronizationContext executionContext) :
            base(newType)
        {
            ExecutionContext = executionContext;
            SetStubData(this, ExecutionContext);
            RealObject = realObject;
        }

        /// <summary>
        /// This method is called on the destination thread to execute the encapsulated
        /// method call.
        /// </summary>
        /// <param name="methodCallData">Reference to a IRemoteMethod interface representing the method to call.</param>
        public void InvokeMethod(Object methodCallData)
        {
            IRemoteMethod methodCall = (IRemoteMethod)methodCallData;

            //-- Constructor calls are handled differently because the real
            //   instance created by the c'tor has to be saved within ReadObject.
            if (methodCall.CallMessage is IConstructionCallMessage)
            {
                methodCall.SetReturnMessage(InitializeServerObject((IConstructionCallMessage)methodCall.CallMessage));
                SetStubData(this, ExecutionContext);
                RealObject = GetUnwrappedServer();
            }
            else
            {
                if (RealObject == null)
                    throw new ThreadBoundException("Object not constructed.");

                //-- Advance the state of the async method call to Running (if the call is synchronous NextState will always return true)
                //   If it was canceled the SetState-Call will return false because "backward" state changes are invalid.
                if (methodCall.SetState(CallStates.Running))
                {
                    //-- Save the current methodCall inside the ThreadGlobals so it can be used
                    //   by ThreadBoundObject.WasCanceled().
                    ThreadGlobals.currentMethod = methodCall;
                    methodCall.SetReturnMessage(RemotingServices.ExecuteMessage(RealObject, methodCall.CallMessage));
                    ThreadGlobals.currentMethod = null;

                    //-- Advance the state further to Done.
                    methodCall.SetState(CallStates.Done);
                }
            }
        }

        /// <summary>
        /// Called by the framework if any of the methods of the intercepted instance is called.
        /// </summary>
        /// <param name="msg">IMessage instance containing all informations necessary to call the method.</param>
        /// <returns>Returns an IMessage instance describing the return value of the method call.</returns>
        public override IMessage Invoke(IMessage msg)
        {
            IMethodCallMessage callMessage = msg as IMethodCallMessage;

            if (callMessage == null)
                throw new ArgumentException("Invoke must be called with an instance that implements IMethodCallMessage.");

            //-- Get the MethodInfo of the method that should be called. This value can be null.
            MethodInfo methodInfo = callMessage.MethodBase as MethodInfo;

            //-- Check if the called method is taged with the FreeThreaded attribute
            bool FreeThreaded = ((methodInfo != null) && (methodInfo.GetCustomAttributes(typeof(FreeThreadedAttribute), true).GetLength(0) > 0));

            //-- If the method is within the same context or it is marked as FreeThreaded we're done and
            //   can directly call InvokeMethod. No context switch, no remoting.
            if (FreeThreaded||ExecutionContext.Equals(SynchronizationContext.Current))
            {
                //-- Ok, same context - we can directly call InvokeMethod
                SyncRemoteMethod syncRemoteMethod = new SyncRemoteMethod(callMessage);
                InvokeMethod(syncRemoteMethod);
                CheckForShutdown(callMessage);
                return syncRemoteMethod.ReturnMessage;
            }
            else
            {
                //-- If the OneWay attribute is set, that call should be done asynchronously.
                if (RemotingServices.IsOneWay(callMessage.MethodBase))
                {
                    //-- Prüfen ob OneWay bei dieser Methode möglich ist. D.h. das kein Rückgabewert und keine Out-Parameter genutzt werden!
                    if (callMessage.ArgCount != callMessage.InArgCount)
                        throw new ThreadBoundException("Out parameters not allowed on OneWay-Call");

                    //-- Jetzt muss es eine Methode mit gültiger MethodInfo sein.
                    if (methodInfo == null)
                        throw new ThreadBoundException("The OneWay attribute can only be used on methods and properties.");

                    //-- Prüfen ob der Asynchrone aufruf das IAsyncCallState-Interface zurückgibt
                    bool usesAsyncCallState=methodInfo.ReturnType.Equals(typeof(IAsyncCallState));

                    //-- Prüfen ob der Rückgabewert void ist. Wenn nicht, kann die Methode logischerweise nicht asynchron aufgerufen werden, da wir keinen
                    //   Rückgabewert hätten.
                    if (!usesAsyncCallState)
                        if (!methodInfo.ReturnType.Equals(typeof(void)))
                            throw new ThreadBoundException("The OneWay attribute can only be used on methods that have no return value or return IAsyncCallState.");

                    //-- Dispose kann nicht asynchron aufgerufen werden
                    if (methodInfo.Name.Equals("Dispose"))
                        throw new ThreadBoundException("Dispose can't be async.");

                    //-- Alles klar, eine AsyncRemoteMethod instanzieren um die CallMessage einzupacken und dann in die Queue des SynchronizationContext stellen.
                    AsyncRemoteMethod asyncMethod=new AsyncRemoteMethod(callMessage);
                    ExecutionContext.Post(InvokeMethod, asyncMethod);

                    //-- If the return value of the function is void we create a dummy ReturnMessage returning nothing.
                    //   If the return value is IAsyncCallState then we cast the created asyncMethod to its IAsyncCallState-Interface and return it to the caller.
                    //   As you can see, the real return value of an asynchronous method is always ignored.
                    if (usesAsyncCallState)
                        return new ReturnMessage(asyncMethod, null, 0, callMessage.LogicalCallContext, callMessage);
                    else
                        return new ReturnMessage(null, null, 0, callMessage.LogicalCallContext, callMessage);
                }
                else
                {
                    SyncRemoteMethod syncRemoteMethod = new SyncRemoteMethod(callMessage);
                    ExecutionContext.Send(InvokeMethod, syncRemoteMethod);
                    CheckForShutdown(callMessage);
                    return syncRemoteMethod.ReturnMessage;
                }
            }
        }

        /// <summary>
        /// Checks if the RealObject implements IDisposable and if Dispose was called.
        /// If that is the case the method checks if the execution context implements IDisposable.
        /// If both conditions are true the Dispose method of the execution context will be called.
        /// </summary>
        /// <param name="callMessage"></param>
        private void CheckForShutdown(IMethodCallMessage callMessage)
        {
            if ((RealObject is IDisposable) && callMessage.MethodName.Equals("Dispose"))
            {
                IDisposable disposableExecContext = ExecutionContext as IDisposable;
                if (disposableExecContext != null) disposableExecContext.Dispose();

                //-- Dispose was called on the ReadObject and the thread has ended.
                //   We set RealObject to null to prevent further calls.
                RealObject=null;
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Systems Engineer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions