.. _workingwithtaskresults: ************************* Working With Task Results ************************* Working with the data sent back from a task is an important element of any application. Overview ======== * :ref:`Task Status` * :ref:`Result Property` * :ref:`StandardOutput and StandardError` * :ref:`Task Properties` * :ref:`Exceptions and Error Handling` * :ref:`Putting it all together` Task Status =========== 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 :py:obj:`COMPLETED `. For details on what each status code means take a look at the :py:class:`TaskStatus ` reference. .. literalinclude:: .\..\..\code\WorkingWithTaskResults.py :language: python :dedent: 12 :lines: 22-26 Result Property =============== The task :py:attr:`result` property is the main mechanism for passing data back after a task completes. To use it, set the :py:attr:`task.result` property at some point during the execution of the task. For example: :: class OurTask(): def execute(self): self.result = "Hello from task" Task results can also be user defined. The only requirement for the result object is that it must be able to be serialized by the pickle serializer. This is an example of a user-defined task. :: from socket import gethostname class SimpleTask: def execute(self): name = gethostname() self.result = MyTaskResult(name) # the result object must be serializable class MyTaskResult: def __init__(self, machine_name): self.time = datetime.now() self.machine_name = machine_name After a task has completed, the client gets a copy of the result object back. To use it, access the :py:attr:`result` property of the task. :: result = job.tasks[0].result print(type(result)) # should be print("Time = ", result.time) print("Machine Name = ", result.machine_name) .. warning:: 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. StandardOutput and StandardError ================================ At times, the easiest way to pass data back is through the :py:attr:`standard_output` and :py:attr:`standard_error` properties. The only thing the task code needs to do is call :py:func:`print` and/or :py:func:`raise`. For example: :: class WritesToStandardOutput: def execute(self): print("Current time in task is: ", datetime.now()) print("Agent machine name: ", gethostname()) The standard output and standard error is sent back to the client as a string. Access it like so: :: print("Standard output: \n" + job.tasks[0].standard_output) .. note:: 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. .. image:: ..\\..\\Media\\ConsoleOutputAppearsInMonitoringApps.png :align: center :alt: ConsoleOutputAppearsInMonitoringApps.png .. note:: In the event of an unhandled exception, the Exception will be written as a string to standard error on the task's behalf. Task Properties =============== Task :py:attr:`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 :py:attr:`result` property. One of the main advantages of :py:attr:`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: :: class PropertiesExampleTask(): def execute(self): self.set_property("Property1", 100) self.set_property("Property2", "one hundred") self.result = "This won't get back to the scheduler" raise Exception("Task failure") On the client side, the result property is an Exception while the properties object returned regardless. :: # after the task failed print("Task status = ", job.tasks[0].task_status) # our result property is an exception print("Exception is " + job.tasks[0].result) # we can still get the properties print("Property1: " + job.tasks[0].properties["Property1"]) print("Property2: " + job.tasks[0].properties["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. .. warning:: Unlike task.result, there are only a small set of supported data types. Use only string, int, float, and byte[]. Exceptions and Error Handling ============================= 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. Task Failure """""""""""" If a task fails, the :py:attr:`result` property of the task will be the Exception object thrown in the task. The :py:class:`TaskStatus ` will be set to :py:attr:`FAILED `. Also, the :py:attr:`standard_error` will include the stack trace of the exception. Here is an example that checks for task failure and inspects the exception. :: if job.tasks[0].task_status == TaskStatus.FAILED or job.tasks[0].task_status == TaskStatus.ENVIRONMENT_ERROR: print("Exception message is: ", job.tasks[0].result) print("StandardError also has the stack trace: ", job.tasks[0].standard_error) .. 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. Task Environment Failure """""""""""""""""""""""" Similar to a task failure, when a task environment fails the task's result is the uncaught exception that was thrown. The :py:class:`TaskStatus ` is set to :py:attr:`ENVIRONMENT_ERROR `. 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. Task Cancellation """"""""""""""""" If a task gets canceled, information on why it was canceled is available. Check the task's :py:attr:`task_cancellation_message` and the :py:attr:`task_cancellation_reason`. Usually, some useful information will be provided in the task_cancellation_message; this normally includes an indication of the user who canceled the job. :: if job.tasks[0].task_status == TaskStatus.CANCELED: print(job.tasks[0].task_cancellation_message) Putting it all together ======================= 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. .. literalinclude:: .\..\..\code\WorkingWithTaskResults.py :language: python :linenos: See Also ======== Reference """"""""" * :py:class:`TaskStatus ` Other Resources """"""""""""""" * :ref:`Monitoring Jobs` * :ref:`Key concepts`