Using the JPPF .Net API
From JPPF 6.3 Documentation
|
Main Page > .Net Bridge > JPPF .Net API |
1 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);
2 Task objects
2.1 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); }
2.2 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); }
2.3 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));
3 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.
3.1 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); ...
3.2 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.
3.3 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.submit(job); for (int i=0; i<results.size(); i++) { Task jppfTask = (Task) results.get(i); BaseDotnetTask actualTask = jppfTask.AsBaseDotnetTask(); // ... process the result ... }
3.4 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());
4 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 |