I'm debating trying to take a crack at getting something similar to C#'s async-co-routine like nature over on JavaFX.
This is technically feasible since the Quantem toolkit exposes the enterNestedEventLoop
and exitNestedEventLoop
methods, but using them is tricky.
If you haven't seen it, C#'s await & task syntax (butchered into Java), looks like this:
public Task<Project> doThing(int countOfSomethingImportant){ return Task.running(self -> { Project project = new Project(eventBus, serializer, graphModel); doInlineTaskThing(countOfSomethingImportant, project); String serialized = serializer.toXMLTask(project).await(); Project deserialized = serializer.fromXMLTask(serialized, Project.class).await(); return deserialized; });}
The idea is that this method can be called from the UI thread, and it's reasonably imperative, so we don't have to deal with the complexity of creating and managing Tasks and worker threads from business logic, we can just call await()
, on a task, which will block the current UI-thread-based job but not the entire UI thread itself.
As a prototype (untested, even functionally), I've come up with this:
class MyTask<T> extends javafx.concurrent.Task<T>{
//a number of other things, including wiring up progress bars
//and special cases for Void return types.
private @WrittenOnce boolean completed = false;
private final Queue<Runnable> completionJobs = new LinkedList<>();
private boolean addCompletionAction(Runnable onCompletion){
synchronized (this){
if( ! completed){
completionJobs.add(onCompletion);
return true;
}
else{
return false;
}
}
}
@Override public void done(){
synchronized (this){
completed = true;
}
completionJobs.forEach(Runnable::run);
}
public static final String SyncingFUBAR =
"we were interrupted while waiting for the signal that a nested event loop was entered. " +
"This (might?) mean that we've just entered a nested event loop we will never exit," +
"alternatively we might have just exited an event-loop we shouldn't have.";
public TResult await(){
try {
if(BootstrappingUtilities.isFXApplicationThread()){
CountDownLatch nestedLoopEnteredSignal = new CountDownLatch(1);
boolean completionRegistered = addCompletionAction(() -> {
logOnException(nestedLoopEnteredSignal::await, SyncingFUBAR, Log);
TResult rval = null;
try{
rval = get();
}
catch (InterruptedException | ExecutionException e) {
//should I suppress this?
throw new RuntimeException(e);
}
finally {
Toolkit.getToolkit().exitNestedEventLoop(this, rval);
}
});
if(completionRegistered){
assert BootstrappingUtilities.isFXApplicationThread();
//enqueing the signal *should* mean we dont have a race condition between count-down and nested-event-loop-enter
Platform.runLater(nestedLoopEnteredSignal::countDown);
return (TResult) Toolkit.getToolkit().enterNestedEventLoop(this);
}
else{
//the task has already completed.
return get();
}
}
else {
return get();
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
catch (CancellationException | ExecutionException e) {
throw new RuntimeException(e);
}
}
Regarding semantics:
- Is this sane?
- Is it worth pursuing the concept of a co-routine between UI thread and worker thread from Java?
- If it is, is it worth trying to formalize the two threads so I have a model-modifying thread and a ui-thread? If I can make this standard it might fix up a whole lot of kloogy model-currency-protection schemes
- What should I do if the UI thread is interrupted while I'm waiting for my latch?
Regarding syntax:
- Is there a way to use an
AtomicBoolean
(or something else?) instead of thatsynchronized(this) flag = whatever
nonsense? - Is there a better tool than a count-down latch?
Regarding testing:
- What's a good way to fuzz this?
1 Answer 1
Note: this is mostly a conventions review.
I find formatting a bit hard to read. Some points:
class MyTask<T> extends javafx.concurrent.Task<T>{
Add a space before the brace:
class MyTask<T> extends javafx.concurrent.Task<T> {
Same with the following lines:
private boolean addCompletionAction(Runnable onCompletion){
...
synchronized (this){
...
public TResult await(){
and other lines, but they have more than this problem, so I will address that separately.
//a number of other things, including wiring up progress bars //and special cases for Void return types. private @WrittenOnce boolean completed = false; private final Queue<Runnable> completionJobs = new LinkedList<>();
End-of-line comments usually have a space between //
and the comment itself:
// a number of other things, including wiring up progress bars
// and special cases for Void return types.
private @WrittenOnce boolean completed = false;
private final Queue<Runnable> completionJobs = new LinkedList<>();
Also, the extra spaces you added for line three of that snipped can be removed without affecting readability:
// a number of other things, including wiring up progress bars
// and special cases for Void return types.
private @WrittenOnce boolean completed = false;
private final Queue<Runnable> completionJobs = new LinkedList<>();
Inside synchronized (this)
:
if( ! completed){ completionJobs.add(onCompletion); return true; } else{ return false; }
A couple of problems:
Line 1: Space between if
and (
Line 1: No extra spaces before and after !
Line 1: Space before brace
Lines 4-5: Should be combined
Line 5: Space before brace
Result:
if (!completed) {
completionJobs.add(onCompletion);
return true;
} else {
return false;
}
In addition to formatting, you can safely omit the else
, as the if
part returns anyways:
if (!completed) {
completionJobs.add(onCompletion);
return true;
}
return false;
} @Override public void done(){ synchronized (this){ completed = true; } completionJobs.forEach(Runnable::run); }
This is the end brace of the previous method plus the new method.
Again, a couple of things:
Lines 1-2: Extra newline between
Line 2: Should be separated into two lines: @Override
and declaration
Line 2: Space before brace
Line 3: Already mentioned, space before brace Line 6: Extra newline serves no purpose, but can be left there if wanted
Result:
}
@Override
public void done() {
synchronized (this) {
completed = true;
}
completionJobs.forEach(Runnable::run);
}
try { if(BootstrappingUtilities.isFXApplicationThread()){ CountDownLatch nestedLoopEnteredSignal = new CountDownLatch(1); boolean completionRegistered = addCompletionAction(() -> { logOnException(nestedLoopEnteredSignal::await, SyncingFUBAR, Log); TResult rval = null; try{ rval = get(); } catch (InterruptedException | ExecutionException e) { //should I suppress this? throw new RuntimeException(e); } finally { Toolkit.getToolkit().exitNestedEventLoop(this, rval); } }); if(completionRegistered){ assert BootstrappingUtilities.isFXApplicationThread(); //enqueing the signal *should* mean we dont have a race condition between count-down and nested-event-loop-enter Platform.runLater(nestedLoopEnteredSignal::countDown); return (TResult) Toolkit.getToolkit().enterNestedEventLoop(this); } else{ //the task has already completed. return get(); } } else { return get(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } catch (CancellationException | ExecutionException e) { throw new RuntimeException(e); }
A couple of problems:
Line 2: Space between if
and (
Line 2: Space before brace
Lines 3, 5, 7, and 9: Newline can be removed but may be left in wanted
Line 11: Space before brace
Lines 13-14: Should be merged
Line 15: Space between //
and comment
Lines 17-18: Should be merged
Line 23: Space between if
and (
Line 23: Space before brace
Line 25: Space between //
and comment
Line 25: typo, I assume? dont -> don't
Line 28-29: Should be merged
Line 30: Space between //
and comment
Line 33-34: Should be merged
Line 37-38: Should be merged
Line 41-42: Should be merged
Result:
try {
if (BootstrappingUtilities.isFXApplicationThread()) {
CountDownLatch nestedLoopEnteredSignal = new CountDownLatch(1);
boolean completionRegistered = addCompletionAction(() -> {
logOnException(nestedLoopEnteredSignal::await, SyncingFUBAR, Log);
TResult rval = null;
try {
rval = get();
} catch (InterruptedException | ExecutionException e) {
// should I suppress this?
throw new RuntimeException(e);
} finally {
Toolkit.getToolkit().exitNestedEventLoop(this, rval);
}
});
if (completionRegistered) {
assert BootstrappingUtilities.isFXApplicationThread();
// enqueing the signal *should* mean we don't have a race condition between count-down and nested-event-loop-enter
Platform.runLater(nestedLoopEnteredSignal::countDown);
return (TResult) Toolkit.getToolkit().enterNestedEventLoop(this);
} else{
// the task has already completed.
return get();
}
} else {
return get();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
} catch (CancellationException | ExecutionException e) {
throw new RuntimeException(e);
}
One other point:
public static final String SyncingFUBAR = "we were interrupted while waiting for the signal that a nested event loop was entered. " + "This (might?) mean that we've just entered a nested event loop we will never exit," + "alternatively we might have just exited an event-loop we shouldn't have.";
Actually, two things:
- Code that form the same command but are on a different line should be 8-spaced, or 2-tabbed.
- Java Naming Conventions specify that
static final
variable should beALL_CAPS_WITH_UNDERSCORE
.
Result:
public static final String SYNCING_FUBAR =
"we were interrupted while waiting for the signal that a nested event loop was entered. " +
"This (might?) mean that we've just entered a nested event loop we will never exit," +
"alternatively we might have just exited an event-loop we shouldn't have.";
-
1\$\begingroup\$ well thanks for your detailed thoughts, out of curiosity is this strict K&R C conventions or is there a java equivalent? We do have a formatter on our project that I haven't run. Interestingly, the one major gripe I've had with all of these conventions has been around the bang-operator. Ive personally fixed three or four bugs relating to a bang that was simply incorrect, and hidden because its a small and unnoticable (and not strongly highlighted in intelli) character. My convention is now to put a space before and after, but some people on my current project write
!!!expression
\$\endgroup\$Groostav– Groostav2015年11月27日 05:16:09 +00:00Commented Nov 27, 2015 at 5:16
Explore related questions
See similar questions with these tags.