Why Synchronous Comms Are Evil

One of the Newton 2.x OS Q&As
Copyright © 1997 Newton, Inc. All Rights Reserved. Newton, Newton Technology, Newton Works, the Newton, Inc. logo, the Newton Technology logo, the Light Bulb logo and MessagePad are trademarks of Newton, Inc. and may be registered in the U.S.A. and other countries. Windows is a registered trademark of Microsoft Corp. All other trademarks and company names are the intellectual property of their respective owners.


For the most recent version of the Q&As on the World Wide Web, check the URL: http://www.newton-inc.com/dev/techinfo/qa/qa.htm
If you've copied this file locally, click here to go to the main Newton Q&A page.
This document was exported on 7/23/97.


Why Synchronous Comms Are Evil (2/1/96)

Q: Why does the following loop run slower and slower with each successive output? If the data variable contains a sufficiently large number of items, the endpoint times out or the Newton reboots before all the data is transmitted. For instance:
    data := [....];
    for item := 0 to Length(data) - 1 do
        ep:Output(data[ item ], nil, nil);

A: When protoBasicEndpoint performs a function synchronously, it creates a special kind of "sub-task" to perform the interprocess call to the comm tool task. The sub-task causes the main NewtonScript task to suspend execution until the sub-task receives the "operation completed" response from the comm tool task, at which time the sub-task returns control to the main NewtonScript task, and execution continues.

The sub-task, however, is not disposed of until control returns to the main NewtonScript event loop. In effect, each and every synchronous call is allocating memory and task execution time until control is returned to the main NewtonScript event loop! For a small number of sucessive synchronous operations, this is fine.

A fully asynchronous implementation, on the other hand, is faster, uses less machine resources, allows the user to interact at any point in the loop, and is generally very easy to implement. The above loop can be rewritten as follows:

ep.fData := [....];
ep.fIndex := 0;
ep.fOutSpec := {
    async:        true,
    completionScript:
        func(ep, options, error)
        if ep.fIndex >= Length(ep.fData) - 1 then
            // indicate we're done
        else
            ep:Output(ep.fData[ ep.fIndex := ep.fIndex + 1 ],
                      nil, ep.fOutSpec )
    };
ep:Output(ep.fData[ ep.fIndex ], nil, ep.fOutSpec );


Of course, you should always catch and handle any errors that may occur within the loop (completionScript) and exit gracefully. Such code is left as an excercise for the reader.