Maintaining a responsive interface

@asyncthread

Use the @asyncthread decorator on a function to make it execute in a thread. The return value is the spawned thread (which can often be ignored by the caller); the return value of the original function is effectively lost.

Cells which are being computed in a separate thread should have that thread as their value until their result is available. This will show the options.disp_pending notation and allow the user to interact with the specific thread (via e.g. z^Y and others).

Each thread is added to Sheet.currentThreads for the current sheet. Note that a thread spawned by calling a function on a different sheet will add the thread to the currentThreads for the topmost/current sheet instead.

Canceling threads

The user can cancel all Sheet.currentThreads with ^C.

Internally, cancelThread(*threads) will send each thread an EscapeException, which percolates up the stack to be caught by the thread entry point. EscapeException inherits from BaseException instead of Exception, so that threads can still have catch-all try blocks with except Exception:. An unqualified except: clause is bad practice (as always); when used in an async function, it will make the thread uncancelable.

Wait for threads to finish

sync(expectedThreads) will wait for all but some number of expectedThreads to finish.

This will only rarely be useful.

Threads Sheet (^T)

All threads (active, aborted, and completed) are added to VisiData.threads, which can be viewed as the ThreadsSheet via ^T. Threads which take less than min_thread_time_s (hardcoded in asyncthread.py to 10ms) are removed, to reduce clutter.

Profiling

The view of a performance profile in VisiData is the output from pstats.Stats.print_stats().

Progress counters

In all @asyncthread functions, a Progress counter should be used to provide a progress percentage, which appears in the right-hand status.

Progress as iterable

When iterating over a potentially large sequence:

for item in Progress(iterable):

This is just like for item in iterable, but it also keeps track of progress, to display on the right status line.

If iterable does not know its own length, it (or an approximation) should be passed as the total keyword argument:

for item in Progress(iterable, total=approx_size):

The Progress object contributes 1 towards the total for each iteration. To contribute a different amount, use Progress.addProgress(n) (n-1 if being used as an iterable, as 1 will be added automatically).

Progress as context manager

To manage Progress without wrapping an iterable, use it as a context manager with only a total keyword argument, and call addProgress as progress is made:

with Progress(total=amt) as prog:
    while amt > 0:
        some_amount = some_work()
        prog.addProgress(some_amount)
        amt -= some_amount