JPPF, java, parallel computing, distributed computing, grid computing, parallel, distributed, cluster, grid, cloud, open source, android, .net
JPPF, java, parallel computing, distributed computing, grid computing, parallel, distributed, cluster, grid, cloud, open source, android, .net
JPPF

The open source
grid computing
solution

 Home   About   Features   Download   Documentation   Forums 

Using the JPPF .Net API

From JPPF 5.1 Documentation

Jump to: navigation, search

Contents

Main Page > .Net Bridge > JPPF .Net API

Initializing the .Net bridge

Before performing any operation with the JPPF APIs, you must initialize the .Net bridge by calling the JPPFDotnet.Init() static method. Here is an example:

using org.jppf.dotnet;

public class MyApp {
  static void Main(string[] args) {
    // initialize the .Net bridge from the configuration
    JPPFDotnet.Init();
    // ... now we can use the JPPF stuff ...
  }
}

This method only needs to be called once in the application's life, and it should be called as early as possible. Upon invocation, it performs the following:

  • read the paths of the required .Net dlls from the application configuration file and register them with the Jni4net bridge
  • read the required Java classpaths from the configuration and configure the created JVM with these paths
  • read the JVM options from the configuration configure the created JVM accordingly
  • create and initialize the JVM so .Net and Java objects can be passed back and forth


For debugging purposes, there is an overload of this method which takes a boolean parameter specifying whether this method should produce verbose output during its execution, as in this example:

// initialize the .Net bridge with verbose output
JPPFDotnet.Init(true);

Task objects

Creating a task

A .Net JPPF task is an object whose class inherits from BaseDotnetTask, defined as follows:

[Serializable()]
public abstract class BaseDotnetTask {
  // Execute this task. This method must be implemented in subclasses
  public abstract void Execute()

  // Called when the cancellation of this task is requested
  public virtual void OnCancel()

  // The cancelled state of this task
  public bool Cancelled { get; }

  // Called when this task times out
  public virtual void OnTimeout()

  // The timed out state of this task
  public bool TimedOut { get; }

  // The exception, if any, raised by the execution of this task
  public Exception Exception { get; }

  // The result, if any, of this task's execution
  public object Result { get; set; }
}

The first thing we can notice is that BaseDotnetTask is Serializable. This is a requirement: since the tasks are intended for execution on remote nodes, they need to be converted to a binary format which can be transported over network connections.

The Execute() method contains the code that will be executed by the remote nodes and must be implemented in all concrete subclasses. Here is an example of a very simple task:

[Serializable()]
public class MyTask : BaseDotnetTask {
  public MyTask() {
  }

  // Execute this task
  public override void Execute() {
    Console.WriteLine("Hello from a .Net task");
    Result = "execution successful";
  }
}

Once a task has been executed, it is returned to the client in its new state. From the client side, you can then get the updated values of its properties and fields, in particular its Result and Exception properties, as in this example:

MyTask task = new MyTask();
// ... execute the task on the grid ...
MyTask resultTask = ...;
if (resultTask.Exception != null) {
  // handle the exception
} else {
  // print out "task result: execution successful"
  Console.Writeline("task result: " + resultTask.Result);
}

Exception handling

Any uncaught exception raised during the excution of the Execute() method is captured by the Exception property, so that it can be sent back to the client side and let the client know that an error occurred. You may also want to capture any exception by yourself from within a try {} catch {} statement and set the value of Exception accordingly. For example:

[Serializable()]
public class MyTask : BaseDotnetTask {
  public override void Execute() {
    try {
      // ... do something ...
      Result = "execution successful";
    } catch (Exception e) {
      Exception = e;
      Result = "execution failure";
    }
  }
}

// on the client side
MyTask resultTask = ...;
if (resultTask.Exception != null) {
  Console.Writeline("task raised an exception " +  resultTask.Exception);
} else {
  Console.Writeline("task result: " + resultTask.Result);
}

Task cancellation and timeout

BaseDotnetTask provides two callback methods, OnCancel() and OnTimeout(), which are invoked whenever a task is cancelled or times out, respectively. These methods can be overriden in subclasses and used to perform cleanup (or other) operations.

Upon cancellation or timeout, the thread executing the task may be interrupted, but only if it is in an interruptible state. In any case, the values or the corresponding read-only properties Cancelled and TimedOut are set accordingly. This allows the code in the tasks to checkfor their cancelled or timeout state at any time.


The task timeout can be set in two ways:


1. With the BaseDotnetTask.TimeoutSchedule property:

public class MyTask : BaseDotnetTask {
  ...
}

MyTask myTask = new MyTask();
// time out after 5 seconds
myTask.TimeoutSchedule = new JPPFSchedule(5000);


2. With the Task instance returned when adding the task to a job:

JPPFJob job = new JPPFJob();
MyTask task = new MyTask();
Task jppfTask = job.add(myTask);
// timeout after 5 seconds
jppfTask.setTimeoutSchedule(new JPPFSchedule(5000));
// or in a single statement:
job.add(new MyTask()).setTimeoutSchedule(new JPPFSchedule(5000));

JPPF jobs with the .Net bridge

JPPF jobs are created and used almost exactly as in Java: by using the class JPPFJob. The differences relate to how tasks are added to a job, how their execution result is accessed and job listeners, detailed in the next sections.

Executing on .Net-capable nodes

The jobs submitted from a .Net client must be executed on .Net-capable nodes, otherwise all their tasks will end in error. To avoid this, you will need to specify an execution policy in the job SLA, which filters out undesirable nodes. Each .Net-capable node has the following boolean configuration property set to true:

jppf.dotnet.bridge.initialized = true

This can used in an execution policy as follows:

JPPFJob job = new JPPFJob();
// execute only on .Net-capable nodes
ExecutionPolicy dotnetPolicy = new Equal("jppf.dotnet.bridge.initialized", true);
job.getSLA().setExecutionPolicy(dotnetPolicy);
// add tasks, submit the job, etc...

If a finer filtering is required, you can always combine the .Net execution policy with other execution policy predicates. For instance, in the following code example we specify that the tasks should only be executed in .Net-capable nodes that have 4 or more processors:

JPPFJob job = new JPPFJob();
ExecutionPolicy dotnetPolicy = new Equal("jppf.dotnet.bridge.initialized", true);
// execute only on .Net-capable nodes with at least 4 CPUs
ExecutionPolicy fullPolicy = dotnetPolicy.and(new AtLeast("availableProcessors", 4));
job.getSLA().setExecutionPolicy(fullPolicy);
...

Adding tasks

As we have seen in the previous section, the tasks must inherit from BaseDotnetTask. To accomodate this, the .Net bridge provides an extension to JPPFJob to add a task to a job:

public static class JPPFJobExtensions {
  // Add a .Net task to the specified job
  public static Task add(this JPPFJob job, BaseDotnetTask task)
}

A typical usage is then like this:

// public class MyTask : BaseDotnetTask { ... }
JPPFJob job = new JPPFJob();
job.setName(".Net job");
Task jppfTask = job.add(new MyTask());

The returned Task object can then be used to set a time out on the task.

Getting the execution results

All the JPPF APIs that provide tasks execution results return a java.util.List of Task objects. Since the tasks initially submitted are subclasses of BaseDotnetTask, an API is required to convert Task objects into BaseDotnetTask objects which can be used on the .Net side. This is implemented as an extension method on Task:

public static class JPPFTaskExtensions {
  // Convert a JPPF Task into a BaseDotnetTask
  public static BaseDotnetTask AsBaseDotnetTask(this Task task)
}

A typical usage would be as follows:

JPPFJob job = new JPPFJob();
Task jppfTask = job.add(new MyTask());
JPPFClient client = new JPPFClient();
java.util.List results = client.submitJob(job);
for (int i=0; i<results.size(); i++) {
  Task jppfTask = (Task) results.get(i);
  BaseDotnetTask actualTask = jppfTask.AsBaseDotnetTask();
  // ... process the result ...
}

Job listeners

Since the .Net bridge does not allow cross-boundaries class inheritance, it is not possible for a .Net class to implement a Java interface. In particular, the JobListener interface cannot be implemented directly. To work around this problem, a specific listener class BaseDotnetJobListener is provided, which is defined as:

public class BaseDotnetJobListener {
  // Called when the job is submitted or re-submitted
  public virtual void JobStarted(JobEvent jobEvent)

  // Called when the job has completed
  public virtual void JobEnded(JobEvent jobEvent)

  // Called when a set of tasks is dispatched to the server
  public virtual void JobDispatched(JobEvent jobEvent)

  // Called when a set of tasks returns from the server
  public virtual void JobReturned(JobEvent jobEvent)
}

As for adding a task, a JPPFJob extension is provided to register a job listener:

public static class JPPFJobExtensions {
  // Add a .Net job listener to the specified job
  public static void addJobListener(this JPPFJob job, BaseDotnetJobListener listener)
}

For example, let's define a job listener which prints job events to the output console:

public class MyJobListener : BaseDotnetJobListener {
  public MyJobListener() {
  }

  public override void JobStarted(JobEvent jobEvent) {
    WriteEvent(jobEvent, "started");
  }

   public override void JobEnded(JobEvent jobEvent) {
    WriteEvent(jobEvent, "ended");
  }

  public override void JobDispatched(JobEvent jobEvent) {
    WriteEvent(jobEvent, "dispatched");
  }

  public override void JobReturned(JobEvent jobEvent) {
    WriteEvent(jobEvent, "returned");
  }

  // Print the specified job event to the console
  public void WriteEvent(JobEvent jobEvent, string type) {
    JPPFJob job = jobEvent.getJob();
    java.util.List tasks = jobEvent.getJobTasks();
    Console.WriteLine("[.Net] Job '" + job.getName() + "' " + type +
      (tasks != null ? " with " + tasks.size() + " tasks" : ""));
  }
}

The following code illustrates how to register this listener with a job:

JPPFJob job = ...;
job.addJobListener(new MyJobListener());

Executor services

The classes JPPFExecutorService and JPPFCompletionService both have a Submit(BaseDotnetTask) extension method which enables them to handle .Net workloads:

public static class JPPFExecutorServiceExtensions {
  // Submit a .Net task with the specified executor service
  public static Future Submit(this JPPFExecutorService executor, BaseDotnetTask task)

  // Submit a .Net task with the specified completion service
  public static Future Submit(
    this JPPFCompletionService completionService, BaseDotnetTask task)
}

Example usage for a JPPFExecutorService:

JPPFClient client = new JPPFClient)(;
JPPFExecutorService executor = new JPPFExecutorService(client);
// send tasks 1 at a time
executor.setBatchSize(1);
IList<Future> futures = new List<Future>();
for (int i=0; i <3; i++) futures.Add(executor.Submit(new MyDotnetTask(100)));
// process results in the order the tasks were submitted
foreach (Future future in futures) {
  // future.get() either returns the value of myTask.Result after execution
  // or throws an eventual exception that was raised
  try {
    object result = future.get();
    // process the result ...
  } catch(Exception e) {
    // handle the exception ...
  }
}
executor.shutdownNow();

And for a JPPFCompletionService:

JPPFClient client = new JPPFClient)(;
JPPFExecutorService executor = new JPPFExecutorService(client);
JPPFCompletionService completionService = new JPPFCompletionService(executor);
// send tasks 3 at a time
executor.setBatchSize(3);
for (int i = 0; i < 3; i++) completionService.Submit(new MyDotnetTask(100));
int count = 0;
// process the results in the order in which they arrive
while (count < 3) {
  // get the next completed task
  Future future = completionService.poll();
  count++;
  try {
    object result = future.get();
    // process the result ...
  } catch(Exception e) {
    // handle the exception ...
  }
}
executor.shutdownNow();
Main Page > .Net Bridge > JPPF .Net API


JPPF Copyright © 2005-2017 JPPF.org Powered by MediaWiki