Working with task results |
Working with the data sent back from a task is an important element of any application.
In this section the following is explained:
The task status describes whether the task completed successfully or encountered errors. If a task completed without throwing an uncaught exception, its status will be Completed. For details on what each status code means take a look at the TaskStatus reference.
if (job.Tasks[0].TaskStatus == TaskStatus.Completed) { // Task executed successfully. // Process results. } else { // There was an issue in the task. // Perform error handling. }
The Result property is the main mechanism for passing data back after a task completes. To use it, set the Result property at some point during the execution of the task. For example:
[Serializable] public class YourTask : Task { public override void Execute() { this.Result = "This is the result from the task."; } }
Task results can also be user defined. The only requirement for the result object is that it must be serialized by the .NET binary serializer. This is an example of a user defined result object:
[Serializable] public class SimpleTask : Task { public override void Execute() { this.Result = new MyTaskResult(Environment.MachineName); } } // The result object must be serializable [Serializable] public class MyTaskResult { public DateTime Time; public string MachineName; public int TickCount; public MyTaskResult(string machineName) { Time = DateTime.Now; MachineName = machineName; TickCount = Environment.TickCount; } }
After a task has completed, the client gets a copy of the result object back. To use it, access the Result property of the task. Note, that because the result property is of the type SystemObject, it will have to be cast to the proper type.
MyTaskResult result = job.Tasks[0].Result as MyTaskResult; // We have a copy of the object back Console.WriteLine("Time = " + result.Time); Console.WriteLine("MachineName = " + result.MachineName); Console.WriteLine("TickCont = " + result.TickCount);
Caution |
---|
The result property may be an Exception type if there was an uncaught exception while executing the task. For more information, see the Exceptions and Error Handling section below. |
At times, the easiest way to pass data back is through the StandardOutput and StandardError properties. The only thing the task code needs to do is call ConsoleWriteLine and/or ConsoleError. For example:
[Serializable] public class WritesToStandardOutput : Task { public override void Execute() { Console.WriteLine("Current Time in task is: " + DateTime.Now); Console.WriteLine("Agent MachineName: " + Environment.MachineName); Console.WriteLine("Current Tick Count is: " + Environment.TickCount); } }
The standard output and standard error is sent back to the client as a string. Access it like so:
Console.WriteLine("Standard Output =" + job.Tasks[0].StandardOutput);
Tip |
---|
The results of standard output are also displayed in the monitoring applications such as the Task Monitor. Looking at the standard output from the GUI is a convenient way to debug your application. |
Note |
---|
In the event of an unhandled exception, the Exception will be written as a string to standard error on the task's behalf. |
Properties are yet another way to return data back from a task to the client. Task properties are stored internally as a hash table and can store user objects such as strings and integers. A task's properties object is returned back to the client after the task completes. Depending on an application's needs, task properties may be more convenient to update than the task's Result property.
One of the main advantages of Properties is that they are guaranteed to be returned to the client even if the task fails. This is not true with the task result. For instance, this example sets both the Result and Properties and then simulates an exception:
[Serializable] public class PropertiesExample : Task { public override void Execute() { this.SetProperty("Property1", 100); this.SetProperty("Property2", "hundred"); this.Result = "This won't get back to the scheduler as we will see"; throw new Exception("Task failure"); } }
On the client side, the Result property is an Exception while the properties object returned regardless.
// After the task failed, Console.WriteLine("Task status = " + job.Tasks[0].TaskStatus); // our Result property is an exception. if (job.Tasks[0].Result is Exception) { Console.WriteLine("Exception is " + job.Tasks[0].Result); } // But we can get the properties object. Console.WriteLine("Property1: " + job.Tasks[0].GetProperty<int>("Property1")); Console.WriteLine("Property2: " + job.Tasks[0].GetProperty<string>("Property2"));
Aside from a guaranteed return after an exception, task properties should only be used if they are more convenient to use than the Result object.
Caution |
---|
Unlike Task.Result, there are only a small set of supported data types. Use only string, int, double, and byte[]. |
Tasks can fail or not complete successfully for a number of reasons. The most common reason for a failure is an uncaught exception in a tasks's code. Fortunately, the task's exception is handled and information is returned back to the client for error handling.
If a task fails, the Result property of the task will be the Exception object thrown in the task. The TaskStatus will be set to Failed. Also, the StandardError will include the stack trace of the exception. Here is an example that checks for task failure and inspects the Exception object.
if ((job.Tasks[0].TaskStatus == TaskStatus.Failed) || (job.Tasks[0].TaskStatus == TaskStatus.EnvironmentError)) { Exception taskException = job.Tasks[0].Result as Exception; if (taskException != null) { Console.WriteLine("Exception message is: " + taskException.Message); Console.WriteLine("StandardError also has the stack trace: " + job.Tasks[0].StandardError); } }
Note |
---|
As with most objects, the Exception object must be serializable. If an Exception object is not serializable, a generic Exception object will be created that includes the error message of the original exception. |
Similar to a task failure, when a task environment fails the task's result is the uncaught exception that was thrown. The TaskStatus is set to EnvironmentError. A task can only run after a task environment is sucessfully setup, so if a task environment fails to setup, the task does not run.
If a task gets canceled, information on why it was canceled is available. Check the task's TaskCancellationMessage and the TaskCancellationReason. Usually, some useful information will be provided in the TaskCancellationMessage; this normally includes an indication of the user who canceled the job.
if (job.Tasks[0].TaskStatus == TaskStatus.Canceled) { if (!string.IsNullOrEmpty(job.Tasks[0].TaskCancellationMessage)) { Console.WriteLine("Task was canceled. Message was: " + job.Tasks[0].TaskCancellationMessage); } }
Below is a code snippet that synthesizes the different ways data can be sent back to the client from a task, as described in this section. The following is a common pattern showing how each of the task statuses is handled after a task completes. Take note that during development, when code is not as well tested and errors are more likely to occur, the cases where a task could fail should always be handled.
// Typical submit and wait pattern.... job.AddTask(task); job.Submit(); job.WaitUntilDone(); // After the task completes... if (task.TaskStatus == TaskStatus.Completed) { // Task ran without throwing an uncaught exception. // This is the only state where your Result object is guaranteed to be your user defined result. Console.WriteLine("Task completed at " + task.GetProperty(TaskProperties.HostEndTime)); Console.WriteLine("Result: " + task.Result); } else if (task.TaskStatus == TaskStatus.Failed) { // Task.Execute threw an uncaught exception. // The result object is the exception that was not caught. Console.WriteLine("Task ({0}) threw an exception during Task.Execute", task.GetType().Name); Console.WriteLine("Exception: " + task.Result); Console.WriteLine("StandardError with callstack: " + task.StandardError); } else if (task.TaskStatus == TaskStatus.EnvironmentError) { // TaskEnvironment.Setup threw an uncaught exception. // The result object is the exception that was not caught. Console.WriteLine("TaskEnvironment ({0}) thew an exception during TaskEnvironment.Setup", job.TaskEnvironment.GetType().Name); Console.WriteLine("Exception: " + task.Result); Console.WriteLine("StandardError with callstack: " + task.StandardError); } else if (task.TaskStatus == TaskStatus.TimedOut) { // Task execution exceeded the value specified in Job.TaskExecutionTimeout // This should only be handled if you specify a value for Job.TaskExecutionTimeout Console.WriteLine("Task execution exceeded the specified execution timeout"); Console.WriteLine("TaskExecutionTimeout was " + job.TaskExecutionTimeout); } else if (task.TaskStatus == TaskStatus.Canceled) { // Task was canceled with Job.Cancel or by the user (for instance, the UI). // This should usually be handled if it is possible that users can cancel your task. Console.WriteLine("Task was canceled"); if (task.TaskCancellationReason == TaskCancellationReason.Direct) { Console.WriteLine("User who canceled the task was: " + task.TaskCancellationMessage); } } else { // Any other state is not a final state, that is the task is still transitioning. }
STK Parallel Computing Server 2.9 API for .NET