Canceling multiple layouts

I have a GoView that allows the user to switch back and forth between a layered digraph layout and a force directed layout. In order to get the same result every time a user switches to the force directed layout, I first run the document through a tree layout to force a locality upon the data. These layout operations can be long running due to the amount data involved, so I give the user a cancel button and run all of the layout on a separate thread. However, a problem arises when the user switches from layered digraph to force directed and then presses the cancel button: the resulting view is the output of the intermediate tree layout, but they (reasonably) expect to see the layered digraph.

What’s the right way to solve this? I tried putting all of the nodes/links into a network and passing the network to the layout objects, but that didn’t seem to have any effect. Do I just need to catch the ThreadAbortException, mark the transaction as finished, and then undo the last transaction?

There are going to be two transactions when you go from layered-digraph to force-directed, since the tree layout has its own transaction in the GoLayoutTree.LayoutNodesAndLinks method.

Remember to do all of the work in the view’s thread. Note: the LayoutNodesAndLinks and OnProgress methods (and thus Progress event handlers) are automatically invoked on the view’s thread, if you have set the View property of the GoLayout object. [This is just in Windows Forms, not in Web Forms.]

So if LayoutNodesAndLinks method and Progress event handlers are running on the view’s thread, you won’t interrupt them by aborting the worker thread that is running the GoLayout. I think it would be easiest to let them finish arranging the nodes and routing the links, and then afterwards undo their transactions.

[QUOTE=walter]Remember to do all of the work in the view’s thread. Note: the LayoutNodesAndLinks and OnProgress methods (and thus Progress event handlers) are automatically invoked on the view’s thread, if you have set the View property of the GoLayout object. [This is just in Windows Forms, not in Web Forms.]

So if LayoutNodesAndLinks method and Progress event handlers are running on the view’s thread, you won’t interrupt them by aborting the worker thread that is running the GoLayout. I think it would be easiest to let them finish arranging the nodes and routing the links, and then afterwards undo their transactions.[/quote]

I understand what you’re saying, but it definitely doesn’t match what I’m seeing. Regardless of when I perform a Thread.Abort() on the layout thread, the result always seems to be the same: I’m left with a tree layout that looks exactly the same whether I cancel the layout at 5% complete or 95% complete.

Letting the layout finish is not really an option as the standard use case is for 5,000+ nodes with 10-15,000 links. The processing time on this layout is simply too long to allow it to continue after the user has requested to cancel the operation.

(I’m using WinForms BTW.)

Right, but then you also need to undo the tree layout too.

(Or are you talking about interrupting the tree layout?)

I’m talking about interrupting the force directed layout. (The tree layout runs so fast that it’s difficult to interrupt – if only all your layout packages approached this speed!) Your initial response made it sound like the layout operation would continue past the point at which the thread was aborted since LayoutNodesAndLinks runs on the View’s thread. However, this doesn’t match what I’m seeing. Have I misunderstood you?

You’re doing two layouts in a row, right?

If it has started the tree layout, you don’t need to undo anything.
If it’s finished the tree layout, you need to undo that.
If it has started the force-directed layout, you only need to undo the tree layout.
If it’s finished the force-directed layout, you need to undo both.

It might be easiest to do both layouts within a single transaction, so that there is just one transaction to consider.

Thanks, I think we’re on the same page now. This is the approach I was considering. One question though: you said in your initial response that GoLayoutTree.LayoutNodesAndLinks generates it’s own transaction. If I put both layouts into a single transaction, will the transaction from LayoutNodesAndLinks create problems? (Sorry, I’m not very familiar with undo/redo in Go.)

Transactions can be nested.

Apparently I’m missing something because I can’t get the transactions to undo. I placed the tree layout inside of Start/FinishTransaction pair and then put the force directed layout inside of a different Start/Finish pair that is not nested. Just before kicking off my layout thread I associated a new GoUndoManager with the Document. Once the user clicks the canel button, I try this on the GUI thread:

 if (this.Document.UndoManager != null)
    this.Document.UndoManager.Undo();

This doesn’t seem to have any effect though. I verified the following in the debugger:

  • UndoManager had a document
  • UndoManager had an edit and it was the edit I wanted
  • EditToUndo was set to the edit I wanted to undo
  • Document.SuspendsUpdates is false
  • Document.SuspendsRouting is false
  • Document.SkipsUndoManager is false

What am I missing? Do layout events not generate any undo information or something?

Each implementation of LayoutNodesAndLinks starts and finishes its own transaction, so you don’t have to.

this.Document.StartTransaction(); GoLayoutTree treelayout = new GoLayoutTree(); ... treelayout.PerformLayout(); GoLayoutLayeredDigraph ldlayout = new GoLayoutLayeredDigraph(); ... ldlayout.PerformLayout(); this.Document.FinishTransaction("layouts");

I had just suggested that you put both consecutive layouts within a single outer transaction. Then when you wanted to abort the thread,

GoUndoManager mgr = this.Document.UndoManager; if (mgr != null && mgr.TransactionLevel > 0) { while (mgr.TransactionLevel > 0) mgr.FinishTransaction("aborting & undoing layout"); mgr.Undo(); }

However, now I’m wondering if my suggestion to wrap an outer transaction around both layouts is wise. Do you expect the user to make any kinds of changes to the document while the layouts are running? I would hope you at least disable any graph-changing operations, such as adding or removing nodes or links.

If you do, then having a long-running transaction will interfere with normal user modifications, such as dragging a node somewhere, since they will be unable to undo/redo.