patternjavaMinor
Generic GUI background task runner (Java 8+)
Viewed 0 times
genericbackgroundjavarunnerguitask
Problem
Here is the culprit; headers omitted for brevity, and also, see notes afterwards:
```
/**
* A GUI-neutral background task executor
*
* A very common scenario when programming with GUI toolkits is the need to
* perform operations in the background without blocking the "UI thread" (this
* would be the infamous EDT, or Event Dispatch Thread, with Swing, or the
* application thread with JavaFX).
*
* Such toolkits, however, always provide a means to postpone tasks to be
* executed on this UI thread; for Swing, that would be {@link
* SwingUtilities#invokeLater(Runnable)}, and for JavaFX, {@link
* Platform#runLater(Runnable)}. What they do not always provide is a tool to
* bind a task to be executed in the background to a related task to be executed
* in the UI thread.
*
* And this is where this class comes in. An instance of this class allows
* you to perform, in a single method call, both the act of invoking the
* background task and schedule the related UI task when the background task is
* done. Example:
*
*
* // If you use JavaFX...
* final BackgroundTaskRunner taskRunner
* = new BackgroundTaskRunner("myapp-%d", Platform::runLater);
* // If you use Swing...
* final BackgroundTaskRunner taskRunner
* = new BackgroundTaskRunner("myapp-%d", SwingUtilities::invokeLater);
*
* // ...
*
* taskRunner.run(
* () -> { my(); background(); task(); here(); },
* () -> { postponed(); ui(); update(); here(); }
* );
*
*
* You can also bind a frontend task to consume a value produced by the given
* backgound task; for instance, if you have two methods:
*
*
* public Foo backgroundProducer()
* {
* // procude a Foo
* }
*
* public void frontendConsumer(final Foo foo)
* {
* // consume a Foo
* }
*
*
* you will then be able to invoke:
*
*
* taskRunner.compute(
* () -> this::backgroundProducer,
* () -> this::frontend
```
/**
* A GUI-neutral background task executor
*
* A very common scenario when programming with GUI toolkits is the need to
* perform operations in the background without blocking the "UI thread" (this
* would be the infamous EDT, or Event Dispatch Thread, with Swing, or the
* application thread with JavaFX).
*
* Such toolkits, however, always provide a means to postpone tasks to be
* executed on this UI thread; for Swing, that would be {@link
* SwingUtilities#invokeLater(Runnable)}, and for JavaFX, {@link
* Platform#runLater(Runnable)}. What they do not always provide is a tool to
* bind a task to be executed in the background to a related task to be executed
* in the UI thread.
*
* And this is where this class comes in. An instance of this class allows
* you to perform, in a single method call, both the act of invoking the
* background task and schedule the related UI task when the background task is
* done. Example:
*
*
* // If you use JavaFX...
* final BackgroundTaskRunner taskRunner
* = new BackgroundTaskRunner("myapp-%d", Platform::runLater);
* // If you use Swing...
* final BackgroundTaskRunner taskRunner
* = new BackgroundTaskRunner("myapp-%d", SwingUtilities::invokeLater);
*
* // ...
*
* taskRunner.run(
* () -> { my(); background(); task(); here(); },
* () -> { postponed(); ui(); update(); here(); }
* );
*
*
* You can also bind a frontend task to consume a value produced by the given
* backgound task; for instance, if you have two methods:
*
*
* public Foo backgroundProducer()
* {
* // procude a Foo
* }
*
* public void frontendConsumer(final Foo foo)
* {
* // consume a Foo
* }
*
*
* you will then be able to invoke:
*
*
* taskRunner.compute(
* () -> this::backgroundProducer,
* () -> this::frontend
Solution
That's some tasty looking code.
I don't think I'm going to be able to find anything wrong with the implementation.
There's something to be said about the documentation, though...
"backgound task", "procude a Foo", lower-case y for "you will then be able to invoke"
... and that's that it's also pretty good, save for those few typo's.
If there's one thing you could do to improve the code, I think it would be providing error messages for
Here's an exception thrown from
It's pretty darn useless at explaining what you did wrong.
Lets say we take your function...
There's 4 parameters here that can give you a
You could save programmers a lot of effort by including a description of what is null. There's many ways to dress it up, and I don't know which one is best, but here's what you ought to do at minimum:
This will include the name of the argument in the stacktrace and makes debugging just that tiny bit easier.
I don't think I'm going to be able to find anything wrong with the implementation.
There's something to be said about the documentation, though...
/** You can also bind a frontend task to consume a value produced by the given
* backgound task; for instance, if you have two methods:
*
*
* public Foo backgroundProducer()
* {
* // procude a Foo
* }
*
* public void frontendConsumer(final Foo foo)
* {
* // consume a Foo
* }
*
*
* you will then be able to invoke: */"backgound task", "procude a Foo", lower-case y for "you will then be able to invoke"
... and that's that it's also pretty good, save for those few typo's.
If there's one thing you could do to improve the code, I think it would be providing error messages for
Objects.requireNotNull. Here's an exception thrown from
Objects.requireNonNull(null):Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at Ideone.main(Main.java:15)It's pretty darn useless at explaining what you did wrong.
Lets say we take your function...
public void computeOrFail(final Runnable before,
final ThrowingSupplier supplier,
final Consumer consumer, final Consumer onError)
{
Objects.requireNonNull(before);
Objects.requireNonNull(supplier);
Objects.requireNonNull(consumer);
Objects.requireNonNull(onError);There's 4 parameters here that can give you a
NullPointerException. And all of them look the same. The only difference given will be the line number.You could save programmers a lot of effort by including a description of what is null. There's many ways to dress it up, and I don't know which one is best, but here's what you ought to do at minimum:
public void computeOrFail(final Runnable before,
final ThrowingSupplier supplier,
final Consumer consumer, final Consumer onError)
{
Objects.requireNonNull(before, "before");
Objects.requireNonNull(supplier, "supplier");
Objects.requireNonNull(consumer, "consumer");
Objects.requireNonNull(onError, "onError");This will include the name of the argument in the stacktrace and makes debugging just that tiny bit easier.
Code Snippets
/** <p>You can also bind a frontend task to consume a value produced by the given
* backgound task; for instance, if you have two methods:</p>
*
* <pre>
* public Foo backgroundProducer()
* {
* // procude a Foo
* }
*
* public void frontendConsumer(final Foo foo)
* {
* // consume a Foo
* }
* </pre>
*
* <p>you will then be able to invoke:</p> */Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at Ideone.main(Main.java:15)public <T> void computeOrFail(final Runnable before,
final ThrowingSupplier<? extends T> supplier,
final Consumer<? super T> consumer, final Consumer<Throwable> onError)
{
Objects.requireNonNull(before);
Objects.requireNonNull(supplier);
Objects.requireNonNull(consumer);
Objects.requireNonNull(onError);public <T> void computeOrFail(final Runnable before,
final ThrowingSupplier<? extends T> supplier,
final Consumer<? super T> consumer, final Consumer<Throwable> onError)
{
Objects.requireNonNull(before, "before");
Objects.requireNonNull(supplier, "supplier");
Objects.requireNonNull(consumer, "consumer");
Objects.requireNonNull(onError, "onError");Context
StackExchange Code Review Q#79154, answer score: 2
Revisions (0)
No revisions yet.