Transactions
are owned by threads. The initial owner of a transaction is the thread in which context
it was created. For the duration of a transaction, the owner thread has exclusive access
to the contents of the editing domain's resource set, except as described below. The EMF
Transaction API provides two mechanisms for multiple threads to cooperatively share access
to the resource set: yielding and privileged runnables. Both of these mechanisms do, however,
maintain the single-threaded model of EMF by ensuring that only one thread at a time is
ever reading or writing.
Yielding Read-only Transactions
Some read-only operations are typically long-running, such as validation (perhaps using the
EMF Validation Framework) or searching (perhaps using the EMF Model Query framework). For
these kinds of operations, Eclipse provides such facilities as the Jobs API and progress
monitors to do work in the background and/or keep the user abreast of what is happening.
Because transactions in a TransactionalEditingDomain lock the
resource set for exclusive access by a single thread, a read-only transaction that runs for
a long time can severely impair the responsiveness of an application's UI if refreshes
require reading the EMF data.
To address these situations, the
TransactionalEditingDomain
provides a yield() method that suspends the current transaction to
allow the next thread waiting for a read-only transaction to start, if there is any such
thread waiting. Only read-only transactions may be yielded in this way, because yielding
any transaction to a writer would result in the data that a reader has already read being
changed, so that when the reader resumes later, its assumptions about the data are invalid.
This phenomenon is known as a "dirty read."
final TransactionalEditingDomain domain;
final IProgressMonitor monitor;
// acquire read transaction on this thread
domain.runExclusive(new Runnable() {
public void run() {
while (moreToRead()) {
// ... do a bunch of reading ...
readSomeStuff();
// checking the progress monitor is a good opportunity to
// yield to other readers
if (monitor.isCancelled()) {
forgetIt();
break;
}
domain.yield(); // just returns if no readers waiting
}
}});
Read access may be yielded to a thread that is waiting to start a read-only transaction or
to a thread that has a read action in progress that is currently suspended on a yield,
waiting to resume it. Yielding proceeds in round-robin fashion amongst the
set of threads currently waiting for or suspending read-only transactions; there is no
facility for prioritization of threads. Any thread
wishing to start a read/write transaction must wait until all active read-only transactions
have completed.
A practical rule of thumb for deciding when to yield a read transaction is to do it whenever
checking a progress monitor for cancellation and/or updating the progress.
Sharing Read/Write Transactions
We have seen how multiple threads with read-only transactions can take turns reading by
cooperatively yielding (suspending) their transactions. It is, however, common in Eclipse
for a thread to be required to synchronously communicate with another thread in order
to accomplish some task.
Probably the most common occurrence of this is in the updating
of UI widgets from background threads or jobs. The SWT API requires that all modifications
to widgets be performed on a thread designated as the Display
thread. What does a thread do if it has a read/write transaction in progress and it
needs to execute code on the UI thread in a Display.syncExec()
call, and this synchronous runnable might need to read the resource set or even modify it?
The thread that owns a transaction can use the
TransactionalEditingDomain.createPrivilegedRunnable(Runnable)
method to wrap an operation (encapsulated in a Runnable) in a
privileged runnable. This privileged runnable, when executed on some other thread,
borrows the original owning thread's transaction for the duration of its execution, which
it delegates to the wrapped runnable. This is ideal for use with APIs such as
Display.syncExec(), in which the thread that lends its transaction
(via the privileged runnable) to the display thread waits for the display thread to finish
executing the runnable before resuming.
Privileged runnables can only be used with synchronous inter-thread communication mechanisms
such as Display.syncExec(). Attempting asynchronous runnables
(as with Display.asyncExec()) will certainly lead to an illegal
transaction state. A thread that posts an asynchronous invocation of a privileged runnable
will proceed with reading and modifying the resource set, only to find suddenly that it
no longers owns the transaction because the other thread has taken it over.
TransactionalEditingDomain domain;
final org.eclipse.swt.widgets.List bookList;
// acquire read transaction on this thread
domain.runExclusive(new Runnable() {
public void run() {
// hand it off to the UI thread to read the library and update
// the list widget
Display.syncExec(domain.createPrivilegedRunnable(
new Runnable() {
public void run() {
// the UI thread now has the transaction to read the resource set
List<String> bookTitles = new ArrayList<String>();
for (Book book : library.getBooks) {
bookTitles.add(book.getTitle());
}
// update the UI
bookList.setItems(bookTitles.toArray(new String[bookTitle.size()]));
} // the UI thread gives up the transaction
}));
Of course, a real application probably would not directly nest runnables in this way.
The code that interacts with the UI thread would more likely be indirectly invoked by a
runExclusive() runnable through some possibly deep call stack.
This mechanism works with both read-only and read/write transactions because only a single
transaction is involved. The privileged runnable differs from yield in being directed at a
specific other thread, which then returns the transaction directly back to the original
owner. We do not have multiple independent threads creating transactions for their own
purposes, but rather a thread that deliberately continues a synchronous operation on
another thread during some interval.
Note that a thread executing a privileged runnable can, in turn, lend the transaction to
yet another thread via another privileged runnable. This can be repeated an arbitrary
number of times, even cycling back to a thread that is already lending the transaction
away in a privileged runnable.
Copyright (c) 2006, 2007 IBM Corporation and others. All Rights Reserved.