Skip to main content

AsyncSwing

Posted by forax on October 1, 2011 at 8:09 AM PDT

At last JVM Summit, I've followed the presentation done by Mads Torgersen about Asynchronous Programming in .NET with the feeeling that while the idea is great,
avoid to explicitly write async callback like by example you do with node.js and let the compiler twist the code for you, but the implementation using two keywords async and await is not good.

The main issue is that the async keyword needs to be declared if a method containing an await thus needs to be propagated back to all methods that call this method recursively. It has exactly the same problem that the checked exceptions in Java, async is a part of the method signature so you can't refactor easily a method in order to add async because you have to back propagate it to all calling methods, if a method overrides a virtual method, you have to declare the virtual method async, you have also to create async delegate to use await in lambda, etc.

Java implementation of generics is done by the compiler, not by the VM, because of that the language is crippled of small stupid limitations that C# doesn't have because generics are reified by the VM in C# and not in Java. .Net guys are doing exactly same mistake here, thinking that the compiler translation is good enough even if it introduces some small glitches like the semantics of the async keyword.
This decision to only rely on the compiler to do the CPS transformation is beyond understanding because mechanism to implement this transformation in the VM (coroutine, continuation) are known since a long time.

As a proof, I propose to implement AsyncSwing a small framework that allows everyone to write Swing applications with tons of IO in the callback of the UI. This framework will trap IOs (the Java 7 File API is pluggable) and generates async IO instead, relying on coroutine (Lukas Stadler has implemented coroutine in the JVM, hoorah) to stop the current callback without stopping the UI event loop. 

Here is an example of code that read a big file 'dict.txt' and append it to a text area.

    UIApplication.start(new Runnable() {
      @Override
      public void run() {
        JFrame frame = new JFrame("test");
        final JTextArea area = new JTextArea();
        final JButton button = new JButton("load dictionnary");
        button.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent _) {
            button.setEnabled(false);
            Path path = Paths.get("dict.txt");
            try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.ISO_8859_1)) {
              String line;
              while ((line=reader.readLine()) != null) {
                area.append(line + '\n');
              }
            } catch (IOException e) {
              e.printStackTrace();
            }
            button.setEnabled(true);
          }
        });
        Container content = frame.getContentPane();
        content.add(BorderLayout.NORTH, button);
        content.add(BorderLayout.CENTER, new JScrollPane(area));
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        frame.setVisible(true);
      }
    });

If you want to test it by yourself, the VM+coroutine is avalaible on Lukas web page and the source code of the AsyncSwing is as attachment of this blog entry.

Known limitations:
The current implementation only trap newInputStream() on a Path and generates one UI event for each async call result instead of trying to reuse one event for several results like the SwingWorker does. 

If you're at JavaOne, Lukas will give a talk about JVM coroutine internals, don't miss it !

cheers,
Rémi

AttachmentSize
async-coro-io.zip707.53 KB
Related Topics >>

Comments

It appears you didn't understand (or Mads didn't explain ...

It appears you didn't understand (or Mads didn't explain well) how the async feature works in C# (and VB). The async keyword isn't part of the method's signature at all. Any method that returns a Task or Task<T> (think of it as a "future") can be declared with the async keyword if you want to use the await keyword in its body. Or it can be declared without the async keyword if you want to manage the asynchrony yourself. The caller can't tell the difference. The "await" unary operator turns a Task<T> into a T by waiting, asynchronously, for the result to become available. But if you don't want that compiler magic, you can provide a lambda to be invoked when the result of a Task<T> is done.

isn't this can be achieved by using InvokeAndLater()&nbsp;an ...

isn't this can be achieved by using InvokeAndLater() an asynchronous call, Can you explain what are the advantage it has over simple invokeAndWait Call ?

Hi Paul, invokeLater is not an asynchronous call because it ...

Hi Paul,
invokeLater is not an asynchronous call because it run in the EDT (Event Dispatch Thread, the thread used to repaint the screen),
it's more a kind of delayed call. Also you can't use invokeAndWait because you can't block the EDT to wait until the EDT run the code.

To take a simpler example, let's say we want to read something on the disk in an UI callback, if you write:

  InputStream input = Files.newInputStream(path);
  input.read(buffer);
  buton.setEnable(true);

You block the EDT until the read finish, so the application is freezed until the read finish.
If you want to do the same asynchronously by hand, you have to write something like:

  EventQueue.invokeLater(new Runnable() {
    public void run() {
       AsyncronousFileChannel channel = AsyncronousFileChannel.open(path);  // let say this can not block
       channel.read(buffer, 0, new CompletionHandler&lt;Integer, Object&gt;() {
         public void completed(Integer result, Object attachment) {
            EventQueue.invokeLater(new Runnable() {  // read completed
              public void run() { // go into the EDT to update the UI
                buton.setEnable(true);
                ...
              }
            });
         }
         ...  // manage exceptions here
       });
    }
  });

The advantage of using some coroutines is that you can keep the stack states when the async operation is under its way
and jump back when the async call finished.

Rémi
 

Hi Neal, I'm sure now that Mads said that T was transformed ...

Hi Neal,
I'm sure now that Mads said that T was transformed to a Task<T>, I've forgotten that point, async is part of the implementation, not a part of the signature.
Anyway, because you have transformed the callee to return a Task you have to transform all the callers to use await (or as you said write the boilerplate required to create a Task by yourself),
then you have to tag all caller implementations with async and so on along all possible stack traces.
So if by example if you want to use a high order methods like map or reduce (or LinQ expressions) in the middle, you have to provide two versions, the one that use a function that return a value and the one that use a function that returns a Task.

There is no reason to create an async keyword if await is handled by the VM, async+Task are artefact only required because the .Net team choose to use a compiler translation,
This decision has two unfortunate consequences, it burden developers because async functions can't be used everywhere in the language without doing some code duplications and it increases the technical debt of C# exactly like non-reified types increase the technical debt of Java.

cheers,
Rémi