September 3, 2014

Consuming Task-based Asynchronous Methods from OpenEdge ABL Part 2

It's been a very long time since the first part but I didn't want this series to remain incomplete. The solution presented in part 1 solved the basic problem and it's successfully used in production for 6 months now, but some topics where omitted for simplicity.

  • How to deal with Task<TResult>?
  • Exception Handling
  • Using Tasks in non UI applications, e.g. AppServer

In this post we will be looking at exception handling and Task<TResult>, that means async methods which "return" a value.

How to get the result

In part 1 the TaskCompleted event was only used for signaling, so the compulsory sender and EventArgs parameters were not of any interest. And it's just the same in this part - at least for the beginning. One might think that we need to pass the result to the event handler using a custom EventArgs class. Altough this has advantages, as we will see later, it's not necessary to access the task result. In fact we don't need any changes on the TaskAwaiter in order to work with Task<TResult>.

Let's do an example. We will be using the following fictive .NET method throughout the examples.

public Task<string> GetStringAsync()

And here's how to use the TaskAwaiter to await the result:

DEF VAR v-awaiter AS TaskAwaiter  NO-UNDO.
DEF VAR v-task    AS "Task<CHAR>" NO-UNDO.

v-awaiter = NEW TaskAwaiter().
v-awaiter:TaskCompleted:SUBSCRIBE("HandleResult").

v-task = GetStringAsync().
v-awaiter:Await(v-task).

PROCEDURE HandleResult:
    DEF INPUT PARAM sender AS System.Object.
    DEF INPUT PARAM args   AS System.EventArgs.

    MESSAGE v-task:Result
        VIEW-AS ALERT-BOX.
END PROCEDURE.

The important part is to store the return value (the task object) of the GetStringAsync method to a variable (v-task), which we can access in the event handling procedure (HandleResult). Using the await include presented at the end of part 1 would save us from some boilerplate code for the TaskAwaiter but the explicit version is better suited for illustration.

Behavior of the result property

In the above example, access to the task (and it's Result property) is not limited to the event handling method. We could as well use the the Result property before the TaskCompleted event is raised. Altough this would work, the TaskAwaiter would become useless because the Result property blocks until the Task is completed. This has the same effect as calling the Wait method on a task.

That doesn't meant using the Result property on a not completed task should never be done, but if you want to await the task, make sure to only access the result after the task is completed.

Exception Handling

If an exception is thrown during task execution which is not caught inside the task, it will not be magically thrown again on the awaiting thread. Instead it will remain unperceived as long as you dont't explicitly check the task's state or access the result property (which will throw an exception on a faulted task).

You can check for unhandeled exceptions using the IsFaulted property of the task. In case it's true the Exception is stored in the eponymous property. Beware that the real Exception is always wrapped with an AggregateException by the TPL. It's available via the InnerException property.

Here's an example displaying the message of the inner exception if the task is faulted:

IF v-task:IsFaulted THEN
    MESSAGE v-task:Exception:InnerException:Message
        VIEW-AS ALERT-BOX.

However AggregateExceptions can contain mulitple exceptions (Stackoverflow: When can an AggregateException contain more than 1 inner exception?):

DEF VAR v-i AS INT NO-UNDO.

IF v-task:IsFaulted THEN DO:

    DO v-i = 0 TO v-task:Exception:InnerExceptions:Count - 1:

        MESSAGE v-task:Exception:InnerExceptions[v-i]:Message
            VIEW-AS ALERT-BOX.
    END.
END.

In case of a Task<TResult> it's not necessary to check state as above. We can just access the Result property and handle potential exceptions the usual way.

Old-fashioned:

DEF VAR v-result AS CHAR NO-UNDO.

v-result = v-task:Result NO-ERROR.

IF ERROR-STATUS:ERROR THEN
    MESSAGE ERROR-STATUS:GET-MESSAGE(1)
        VIEW-AS ALERT-BOX.

Modern:

DEF VAR v-error  AS Progress.Lang.Error NO-UNDO.
DEF VAR v-result AS CHAR               NO-UNDO.

v-result = v-task:Result.

CATCH v-error AS Progress.Lang.Error:

    MESSAGE v-error:GetMessage(1)
        VIEW-AS ALERT-BOX.
END CATCH.

But, even in this case the AggregateException is thrown, not the real one.

If you have a non generic task (without Result) and want to use the normal error handling instead of checking the IsFaulted property, there is a trick to throw the exception. Just invoke Wait() on the task after it's completed. It will do nothing if the task is completed successfully, but it will throw the AggregateException if it's faulted:

DEF VAR v-error  AS Progress.Lang.Error NO-UNDO.

v-task:Wait().

CATCH v-error AS Progress.Lang.Error:

    MESSAGE v-error:GetMessage(1)
        VIEW-AS ALERT-BOX.
END CATCH.

Minimizing Scope

The solution presented above works, but we need a global variable to hold the task or an instance variable when used in a class. In most cases it would be sufficient to access the task in the event handling procedure / method (HandleResult in the example above). To achieve this we can change the EventArgs type of the TaskCompleted event to a custom type containing a Task property.

The extended TaskAwaiter including the new EventArgs class (changed lines in bold):

public class TaskAwaiter
{
    public event EventHandler<TaskCompletedEventArgs> TaskCompleted;

    public void Await(Task task)
    {
        task.ContinueWith(
            t => OnTaskCompleted(t),
            TaskScheduler.FromCurrentSynchronizationContext());
    }

    private void OnTaskCompleted(Task task)
    {
        var handler = TaskCompleted;
        if (handler != null)
            TaskCompleted(this, new TaskCompletedEventArgs(task));
    }
}

public class TaskCompletedEventArgs : EventArgs
{
    public Task Task { get; private set; }

    public TaskCompletedEventArgs(Task task)
    {
        Task = task;
    }
}

This allows us to use the task in the event handling procedure as follows:

PROCEDURE HandleResult:
    DEF INPUT PARAM sender AS System.Object.
    DEF INPUT PARAM args   AS TaskCompletedEventArgs.

    IF args:Task:IsFaulted THEN
        ...

To access the Result property of a Task<TResult> we need to cast:

DEF VAR v-result AS CHAR NO-UNDO.

v-result = CAST(args:Task, "Task<CHAR>"):Result.

Adding a state parameter

When working with events it's not unusual to offer a state parameter which allows the user to transfer an arbitrary object to the event handling code. Otherwise this would lead to global state as seen with the task above.

That's why I added an overload to the Await method which takes a state parameter:

public class TaskAwaiter
{
    public event EventHandler<TaskCompletedEventArgs> TaskCompleted;

    public void Await(Task task)
    {
        Await(task, null);
    }

    public void Await(Task task, object state)
    {
        task.ContinueWith(
            t => OnTaskCompleted(t, state),
            TaskScheduler.FromCurrentSynchronizationContext());
    }

    private void OnTaskCompleted(Task task, object state)
    {
        var handler = TaskCompleted;
        if (handler != null)
            TaskCompleted(this, new TaskCompletedEventArgs(task, state));
    }
}

public class TaskCompletedEventArgs : EventArgs
{
    public Task Task { get; private set; }
    public object State { get; private set; }

    public TaskCompletedEventArgs(Task task, object state)
    {
        Task = task;
        State = state;
    }
}

Here's an example how it could be used from OpenEdge:

DEF VAR v-awaiter AS TaskAwaiter NO-UNDO.

v-awaiter = NEW TaskAwaiter().
v-awaiter:TaskCompleted:SUBSCRIBE("HandleResult").
v-awaiter:Await(GetStringAsync(), "useful object").

PROCEDURE HandleResult:
    DEF INPUT PARAM sender AS System.Object.
    DEF INPUT PARAM args   AS TaskCompletedEventArgs.

    MESSAGE args:State
        VIEW-AS ALERT-BOX.

END PROCEDURE.

This may seem like overkill in a simple procedural example, but it's definitely useful when working with classes or in a more complex procedure than this.

However you can only pass simple ABL types like char and int (which have a .NET type mapping) and objects derived from System.Object. Instances of Progress.Lang.Object can't be referenced from .NET (...it would be possible with a wrapper class but this is another topic).

Adapting the await include

Using the TaskAwaiter without the await include presented in part 1 introduces a lot of boilerplate code. So we should change it to support the optional state parameter:

&SCOPED-DEFINE id {&SEQUENCE}   

DEF VAR v-awaiter{&id} AS TaskAwaiter NO-UNDO.

v-awaiter{&id} = NEW TaskAwaiter().
v-awaiter{&id}:TaskCompleted:Subscribe({2}).

&IF "{3}" = "" &THEN
v-awaiter{&id}:Await({1}).
&ELSE
v-awaiter{&id}:Await({1}, {3}).
&ENDIF

Then we end up with this in OpenEdge:

{await GetStringAsync() 'HandleResult' 'useful value'}

PROCEDURE HandleResult:
    DEF INPUT PARAM sender AS System.Object.
    DEF INPUT PARAM args   AS TaskCompletedEventArgs.

    MESSAGE args:State
        VIEW-AS ALERT-BOX.

END PROCEDURE.

Links

Consuming Task-based Asynchronous Methods from OpenEdge ABL Part 1

February 13, 2014

The wonderful side benefit of NoSplash

Have you ever wondered why the progress runtime (prowin32.exe to be precise) takes such a long time to start? Especially in comparison to other runtimes like JVM or CLR?

The surprising answer is: It doesn't take long. It just sleeps for 2 seconds while showing the well known blue splash screen (or the orange one if you are on the latest version).
Progress splash image

On my machine it takes 2.15 seconds to start prowin and run a simple procedure containing just a QUIT statement. If we turn off the splash screen by using NoSplash=yes in our progress.ini file, it only takes 150 ms.

To get rid of the splash screen completely - e.g. on your development machine - you don't have to create a progress.ini file for each application. Just add a new String Value named NoSplash to the following registry key and assign yes:

HKEY_CURRENT_USER\Software\PSC\PROGRESS\YourVersion\Startup

This way the 2 second penalty even vanishes when running a program from Developer Studio, where it annoyed me the most.
Progress Developer Studio

Should you already be using a custom ini file with your application (loaded by -basekey INI -ininame file.ini) , the registry settings have no effect. In this case, you have to add the NoSplash entry to the corresponding ini file.

If you ask me, as a developer, Progress should change the default setting and turn the splash screen off, at least for the Developer Studio. And if that is not going to happen, what i assume, please document it more prominent.

I suppose I am not the only OpenEdge Developer who was annoyed by the slow startup, and unaware of the solution. In fact, I knew the NoSplash option for quite a while, but I was thinking that it would only disable the splash screen (not my intent), which kept me from trying it out.

Even if I had checked the Documentation, I doubt it would have put me on the right track. The current description of NoSplash from Deployment: Managing ABL Applications:

Set to YES to turn off display of the splash screen. By default, the OpenEdge GUI client and the WebClient display a splash screen during startup. The screen, which is a blue rectangle with the words “Powered by Progress Software,” disappears when the OpenEdge application starts.

I think it could be described more clearly. Especially the diction "during startup" underpins my wrong intuitive comprehension.

Update

Vote for the idea in the Progress Community: Turn off the splash screen for developers
See the related community discussion: The wonderful side benefit of NoSplash

February 9, 2014

Consuming Task-based Asynchronous Methods from OpenEdge ABL Part 1

The Task-based Asynchronous Pattern (TAP) was introduced in .NET 4.0 as part of the Task Parallel Library (TPL), which was one of the key features in this release. Methods implementing the TAP pattern can be recognized by their return type System.Threading.Tasks.Task and an "Async" suffix on the method name, if they follow the convention. Tasks are a great abstraction for the complexities of multithreading and asynchrony. Compared with the older patterns in the .NET Framework (EAP and APM) they are a lot easier to use, require less code and are more powerful.

Why should I care as an OpenEdge developer?

Because of the growing importance of multithreading and asynchrony tasks are becoming a substantial part of .NET, which makes them harder and harder to avoid. If we can't use tasks as OpenEdge developers we could only access a shrinking part of .NET via the CLR bridge. Tasks are also a great way to expose self written multithreaded C# code to the single threaded world of OpenEdge.

Let's try it

.NET 4.0 libraries can be referenced from OpenEdge 11.0 onwards. I assume that everything in this post is true for OE 11.0 to 11.3, and I checked the documentation from 11.0 and 11.3 to ensure that, but I only have 11.1 available for testing.

To keep things simple in the first part I will concentrate on tasks without results and leave Task<T> for the next part. For illustration purposes I will use a fictive method with the following signature: public Task DoAsync().

Our goal for this post is to invoke the DoAsync method from ABL, and invoke a method named Done (in the same class) when the task is completed.

In C# (.NET 4.0) it is done like this:

DoAsync().ContinueWith(t => Done());

Invoking the DoAsync method from ABL is no problem, but we don't know when the task has finished. So we need the ContinueWith method as in the C# example. Code completion in OpenEdge Developer Studio lists some overloads for this method. The simplest requires one parameter of type Action<Task>: ContinueWith overloads from OpenEdge Developer Studio

Although generic .NET classes can be instantiated in ABL, and this one looks like a generic class, it will not work with this one because Action is a delegate and not a class.

The OpenEdge documentation (GUI for .NET Programming > Limitations of support for .NET classes) says:

You cannot use an instance of System.Delegate or any delegate type derived from it. However, when you implement a .NET abstract or interface event in ABL, you must make reference to a delegate type in order to specify the event signature.

What is a delegate?

I like to describe delegates as method pointer objects. Or more precise from the msdn documentation:

A delegate in C# is similar to a function pointer in C or C++. Using a delegate allows the programmer to encapsulate a reference to a method inside a delegate object. The delegate object can then be passed to code which can call the referenced method, without having to know at compile time which method will be invoked. Unlike function pointers in C or C++, delegates are object-oriented, type-safe, and secure.

What can we do now?

Now we know what a delegate is and that we need one to use the ContinueWith method. But using delegates is not possible in ABL.

So we need some kind of adapter that allows us to create a .NET delegate which points to ABL code.

As we have seen in the example it is very easy to create a delegate in C#. But how can we invoke ABL code from .NET?

There are three different possibilities:

  1. Implementing a .NET interface from an ABL class.
  2. Creating an ABL class which inherits from a .NET class.
  3. Subscribing to a .NET event in ABL.

Solutions 1 and 2 share a great disadvantage as they dictate the method name, and therefore do not allow more than one method per class. That's why we will take the event which is the most flexible option.

This would lead us to a delegate-event adapter. Even though this is an interesting topic, and would solve our problem, I want to keep things simple and focus on Tasks this time. Thus we will be able to optimize our solution for tasks which will be easer to implement and - more important - easier to use.

Less abstract we need something that takes a task, exposes an event and publishes that event when the task is completed.

The TaskAwaiter

public class TaskAwaiter
{
    public event EventHandler TaskCompleted;

    public void Await(Task task)
    {
        task.ContinueWith(t => OnTaskCompleted());
    }

    private void OnTaskCompleted()
    {
        var handler = TaskCompleted;
        if (handler != null)
            TaskCompleted(this, EventArgs.Empty);
    }
}

This little C# class meets the above requirements. The Await method takes a task and registers a continuation task, which publishes the TaskCompleted event by invoking the TaskCompleted method.

If you are curious why I used the EventHandler delegate with it's two parameters seeming quite useless in this case: There is a reason for that. .Net events used from ABL must have the Object, EventArgs signature (of course it's possible to subclass EventArgs). The ABL compiler will not complain about other event signatures, but at runtime your subscribers won't get invoked.

Threading Issues

As implied by the caption the TaskAwaiter is not ready yet. Running the above version will present the following error message: ABL wrong thread error

The problem is that the continuation task, which publishes the event we subscribed to in ABL, is not executed on the ABL thread. This is because we used the one parameter version of ContinueWith. We just specified the task to run (invoke TaskCompleted), but not how. That's why the task was executed on a thread pool thread and not on the ABL thread, which invoked the Await method.

Invoking on the right thread

So we need to add more parameters to the ContinueWith method in order to run the continuation on the ABL thread. This raises two questions:

  1. How to capture the right thread?
  2. How to pass that information to ContinueWith?

We know that ContinueWith is called on the ABL thread so we could capture the thread via Thread.CurrentThread. However, this has no value because it is not possible to execute code on an existing thread. The only way is to already have code running in the target thread which allows other threads to communicate with it.

In case of a .NET UI thread the work has already been done. Windows Forms offers Control.Invoke and WPF Dispatcher.Invoke to execute code on the UI thread.

Simply put, if you invoke one of these methods, a window message is sent to the corresponding window, which then gets processed by the message loop running in the UI thread, which will invoke the desired code.

This explains why we can't use Thread.CurrentThread, but how do we capture the UI thread instead? We need to use SynchronizationContext.Current, which returns the SynchronizationContext that is attached to the current thread.

A SynchronzationContext is an abstraction of a thread that has a mechanism which allows other threads to execute code on it. There are implementations of SynchronzationContext for WinForms and WPF based on Control.Invoke and Dispatcher.Invoke.

Now the second question: How do we pass the captured SynchronzationContext to ContinueWith?

Although the method has some overloads, no one accepts a SynchronzationContext. The appropriate variant in this case takes a TaskScheduler as second parameter.

A TaskScheduler is responsible for executing tasks. It controls when and how (on which thread) a task is run. This sounds as we need a TaskScheduler that executes our task using the captured SynchronizationContext.

Fortunately we don't need to write it ourselves, because it's available already. The static method TaskScheduler.FromCurrentSynchronizationContext returns a TaskScheduler that captures the current SynchronizationContext and uses it to execute the tasks.

So, all we have to do is to pass a second parameter to ContinueWith:

task.ContinueWith(
    t => OnTaskCompleted(),
    TaskScheduler.FromCurrentSynchronizationContext());

Getting a SynchronizationContext

If run in an OpenEdge application that has a .NET UI, the fixed TaskAwaiter will just work. If it's invoked before the .NET UI gets created or in an old Progress GUI application, you will see the following error message: SynchronizationContext error

Taken literally, I understand that the current thread has a SynchronizationContext, but it can not be used for task scheduling. In fact, SynchronizationContext.Current is returning null, which is the default behavior for a thread.

So, why does the UI thread have a SynchronizationContext after UI creation? The first Control instance creates a SynchronizationContext and attaches it to the current thread. To solve our problem in a Progress GUI application, we can do this by ourselves:

SynchronizationContext.SetSynchronizationContext(
    NEW WindowsFormsSynchronizationContext()).

Creating a Control - for example NEW Form() - would have the same effect, but I think although it's less code, it would look very strange because the intent is not very clear.

Until now I have not investigated using tasks with character or non UI OpenEdge applications (e.g. AppServer).

Using the TaskAwaiter

DEF VAR v-awaiter AS TaskAwaiter NO-UNDO.

v-awaiter = NEW TaskAwaiter().
v-awaiter:TaskCompleted:Subscribe(Done).
v-awaiter:Await(someObject:DoAsync()).

As you see, using the TaskAwaiter from ABL is pretty easy. We are creating an instance of the TaskAwaiter, subscribe the Done method to the TaskCompleted event and pass the task, which the DoAsync method is returning, to the Await method.

The signature of the Done method:

METHOD PRIVATE VOID Done(sender AS System.Object, args AS System.EventArgs)

Using the TaskAwaiter with less Code

Compared to the one liner in C# this 4 lines contain a lot of boilerplate code, which makes it harder for the reader to see the relevant parts. Thus, I decided to create the following small include:

&SCOPED-DEFINE id {&SEQUENCE}   

DEF VAR v-awaiter{&id} AS TaskAwaiter NO-UNDO.

v-awaiter{&id} = NEW TaskAwaiter().
v-awaiter{&id}:TaskCompleted:Subscribe({2}).
v-awaiter{&id}:Await({1}).

Thats the same code as above except that I am using the built-in preprocessor name &SEQUENCE to make the variable name unique. Without it the include could be used only once per scope.

To make it look more like a statement I omitted the usual .i extension and just named the include file await. Using the include we get the final line of code:

{await someObject:DoAsync() Done}