mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
[220446] Updated the "DSF Common Patterns" document.
This commit is contained in:
parent
f4f4a35474
commit
90f4dc4c71
9 changed files with 88 additions and 339 deletions
|
@ -3,16 +3,17 @@
|
|||
<booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
|
||||
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/org.eclipse.dd.examples.dsf"/>
|
||||
<listEntry value="/org.eclipse.dd.examples.dsf/build_preprocess.xml"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.dd.examples.dsf"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <launchConfigurationWorkingSet factoryID="org.eclipse.ui.internal.WorkingSetFactory" label="workingSet" name="workingSet"> <item factoryID="org.eclipse.ui.internal.model.ResourceFactory" path="/org.eclipse.dd.examples.dsf/src_preprocess" type="2"/> </launchConfigurationWorkingSet>}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/org.eclipse.dd.examples.dsf/build_preprocess.xml}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value=""/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,"/>
|
||||
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
|
||||
</launchConfiguration>
|
||||
|
|
|
@ -21,6 +21,8 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.dd.dsf.concurrent.ConfinedToDsfExecutor;
|
||||
import org.eclipse.dd.dsf.concurrent.ThreadSafe;
|
||||
import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
|
||||
import org.eclipse.dd.dsf.concurrent.DsfExecutor;
|
||||
import org.eclipse.dd.dsf.concurrent.ImmediateExecutor;
|
||||
|
@ -51,10 +53,12 @@ import org.eclipse.swt.widgets.Table;
|
|||
* to check the canceled state of the requests and ignore them.
|
||||
* </p>
|
||||
*/
|
||||
@ConfinedToDsfExecutor("fDisplayExecutor")
|
||||
public class AsyncDataViewer
|
||||
implements ILazyContentProvider, IDataGenerator.Listener
|
||||
{
|
||||
// Executor to use instead of Display.asyncExec().
|
||||
@ThreadSafe
|
||||
final private DsfExecutor fDisplayExecutor;
|
||||
|
||||
// The viewer and generator that this content provider using.
|
||||
|
@ -106,10 +110,12 @@ public class AsyncDataViewer
|
|||
return Math.min((table.getBounds().height / table.getItemHeight()) + 2, itemCount - top);
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void countChanged() {
|
||||
queryItemCount();
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void valuesChanged(final Set<Integer> indexes) {
|
||||
// Mark the changed items in table viewer as dirty, this will
|
||||
// trigger update requests for these indexes if they are
|
||||
|
|
|
@ -50,7 +50,7 @@ import org.eclipse.dd.examples.dsf.DsfExamplesPlugin;
|
|||
* </p>
|
||||
*/
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//#@ThreadSafe
|
||||
//#endif
|
||||
|
@ -59,7 +59,8 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
// Request objects are used to serialize the interface calls into objects
|
||||
// which can then be pushed into a queue.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
// Hint: Request and its subclasses have all their fields declared as final.
|
||||
//#else
|
||||
//# @Immutable
|
||||
//#endif
|
||||
|
@ -72,7 +73,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @Immutable
|
||||
//#endif
|
||||
|
@ -83,7 +84,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @Immutable
|
||||
//#endif
|
||||
|
@ -97,7 +98,10 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
|
||||
// The executor used to access all internal data of the generator.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotation indicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotation indicating allowed concurrency access
|
||||
// Hint: If a member does not have an annotation, the programmer can assume
|
||||
// that the concurrency rule that applies to the class also applies to this
|
||||
// member.
|
||||
//#endif
|
||||
private DsfExecutor fExecutor;
|
||||
|
||||
|
@ -106,7 +110,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
// method reads from it.
|
||||
// The executor used to access all internal data of the generator.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -115,7 +119,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
// List of listeners is not synchronized, it also has to be accessed
|
||||
// using the executor.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -123,7 +127,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
|
||||
// Current number of elements in this generator.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -131,7 +135,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
|
||||
// Counter used to determine when to reset the element count.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -139,7 +143,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
|
||||
// Elements which were modified since the last reset.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -147,14 +151,14 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
|
||||
// Flag used to ensure that requests are processed sequentially.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
private boolean fServiceQueueInProgress = false;
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotation indicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotation indicating allowed concurrency access
|
||||
//#endif
|
||||
public DataGeneratorWithExecutor() {
|
||||
// Create the executor
|
||||
|
@ -173,7 +177,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotation indicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotation indicating allowed concurrency access
|
||||
//#endif
|
||||
public void shutdown(final RequestMonitor rm) {
|
||||
try {
|
||||
|
@ -199,7 +203,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotation indicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotation indicating allowed concurrency access
|
||||
//#endif
|
||||
public void getCount(final DataRequestMonitor<Integer> rm) {
|
||||
try {
|
||||
|
@ -216,7 +220,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotation indicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotation indicating allowed concurrency access
|
||||
//#endif
|
||||
public void getValue(final int index, final DataRequestMonitor<String> rm) {
|
||||
try {
|
||||
|
@ -233,7 +237,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotation indicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotation indicating allowed concurrency access
|
||||
//#endif
|
||||
public void addListener(final Listener listener) {
|
||||
try {
|
||||
|
@ -246,7 +250,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotation indicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotation indicating allowed concurrency access
|
||||
//#endif
|
||||
public void removeListener(final Listener listener) {
|
||||
try {
|
||||
|
@ -260,14 +264,18 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
|
||||
// Main processing function of this generator.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
private void serviceQueue() {
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 4 - Add logic to discard requests from queue.
|
||||
// TODO Excercise 3 - Add logic to discard requests from queue.
|
||||
// Hint: Since serviceQueue() is called using the executor, and the
|
||||
// fQueue list can only be modified when running in the executor
|
||||
// thread. This method can safely iterate and modify fQueue without
|
||||
// risk of race conditions or concurrent modification exceptions.
|
||||
//#else
|
||||
//# for (Iterator<Request> requestItr = fQueue.iterator(); requestItr.hasNext();) {
|
||||
//# Request request = requestItr.next();
|
||||
|
@ -311,7 +319,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -324,7 +332,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
}
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -344,7 +352,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
* This method simulates changes in the supplier's data set.
|
||||
*/
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -362,7 +370,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
* Calculates new size for provider's data set.
|
||||
*/
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
@ -384,7 +392,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
|
|||
* Invalidates a random range of indexes.
|
||||
*/
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
// TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//# @ConfinedToDsfExecutor("fExecutor")
|
||||
//#endif
|
||||
|
|
|
@ -18,7 +18,9 @@ import java.util.Set;
|
|||
|
||||
import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
|
||||
import org.eclipse.dd.dsf.concurrent.RequestMonitor;
|
||||
import org.eclipse.dd.dsf.concurrent.ThreadSafe;
|
||||
//#ifdef answers
|
||||
//#import org.eclipse.dd.dsf.concurrent.ThreadSafe;
|
||||
//#endif
|
||||
|
||||
/**
|
||||
* Data generator is simple source of data used to populate the example table
|
||||
|
@ -28,7 +30,7 @@ import org.eclipse.dd.dsf.concurrent.ThreadSafe;
|
|||
* is changed.
|
||||
*/
|
||||
//#ifdef excercises
|
||||
//TODO Excercise 3 - Add an annotationindicating allowed concurrency access
|
||||
//TODO Excercise 4 - Add an annotationindicating allowed concurrency access
|
||||
//#else
|
||||
//#@ThreadSafe
|
||||
//#endif
|
||||
|
|
|
@ -122,6 +122,12 @@ public class SyncDataViewer
|
|||
}
|
||||
|
||||
private void refreshViewer() {
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 5 - Add a call to getElements() to force a deadlock.
|
||||
//#else
|
||||
//# getElements(null);
|
||||
//#endif
|
||||
|
||||
// This method may be called on any thread, switch to the display
|
||||
// thread before calling the viewer.
|
||||
Display display = fViewer.getControl().getDisplay();
|
||||
|
@ -148,7 +154,12 @@ public class SyncDataViewer
|
|||
tableViewer.getControl().setLayoutData(data);
|
||||
|
||||
// Create the data generator.
|
||||
//#ifdef excercises
|
||||
// TODO Excercise 5 - Use the DataGeneratorWithExecutor() instead.
|
||||
final IDataGenerator generator = new DataGeneratorWithThread();
|
||||
//#else
|
||||
//# final IDataGenerator generator = new DataGeneratorWithExecutor();
|
||||
//#endif
|
||||
|
||||
// Create the content provider which will populate the viewer.
|
||||
SyncDataViewer contentProvider = new SyncDataViewer(tableViewer, generator);
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.1 KiB |
|
@ -1,289 +0,0 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=ISO-8859-1"
|
||||
http-equiv="content-type">
|
||||
<title>DSF Slow Data Provider Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<p class="MsoNormal" style="line-height: normal;"><b><span
|
||||
style="font-size: 12pt; font-family: "Times New Roman";">Version
|
||||
1.0<br>
|
||||
Pawel Piech<br>
|
||||
© 2006, Wind River Systems.<span style=""> </span>Release
|
||||
under EPL version 1.0.</span></b><b><span
|
||||
style="font-size: 18pt; font-family: "Times New Roman";"><o:p></o:p></span></b></p>
|
||||
<h3>Slow Data Provider Example</h3>
|
||||
The point of DSF concurrency can be most easily explained through
|
||||
a practical example. Suppose there is a viewer which needs to
|
||||
show data that originates from a remote "provider". There is a
|
||||
considerable delay in transmitting the data to and from the provider,
|
||||
and some delay in processing the data. The viewer is a
|
||||
lazy-loading table, which means that it request information only about
|
||||
items that are visible on the screen, and as the table is scrolled, new
|
||||
requests for data are generated. The diagram below illustrates
|
||||
the
|
||||
logical relationship between components:<br>
|
||||
<br>
|
||||
.<img alt="" title="Slow Data Provider Diagram"
|
||||
src="doc-files%5Cdsf_concurrency_model-1.png"
|
||||
style="width: 636px; height: 128px;"><br>
|
||||
<p>In detail, these components look like this:<span
|
||||
style="text-decoration: underline;"></span></p>
|
||||
<p><span style="text-decoration: underline;"></span></p>
|
||||
<span style="text-decoration: underline;">Table Viewer</span><br>
|
||||
<p>The table viewer is the standard
|
||||
<span style="font-family: monospace;">org.eclipse.jface.viewers.TableViewer</span>,
|
||||
created with <span style="font-family: monospace;">SWT.VIRTUAL</span>
|
||||
flag. It has an associated content
|
||||
provider, SlowDataProviderContentProvider) which handles all the
|
||||
interactions with the data provider. The lazy content provider
|
||||
operates in a very simple cycle:</p>
|
||||
<ol>
|
||||
<li>Table viewer tells content provider that the input has changed by
|
||||
calling <span style="font-family: monospace;">IContentProvider.inputChanged()</span>.
|
||||
This means that the content provider has to query initial state of the
|
||||
data.</li>
|
||||
<li>Next the content provider tells the viewer how many elements
|
||||
there are, by calling <span style="font-family: monospace;">TableViewer.setItemCount()</span>.</li>
|
||||
<li>At this point, the table resizes, and it requests data values for
|
||||
items that are visible. So for each visible item it calls: <span
|
||||
style="font-family: monospace;">ILazyContentProvider.updateElement()</span>.</li>
|
||||
<li>After calculating the value, the content provider tells the table
|
||||
what the value is, by calling <span style="font-family: monospace;">TableViewer.replace().</span></li>
|
||||
<li>If the data ever changes, the content provider tells the table to
|
||||
rerequest the data, by calling <span style="font-family: monospace;">TableViewer.clear()</span>.</li>
|
||||
</ol>
|
||||
Table viewer operates in the
|
||||
SWT display thread, which means that the content provider must switch
|
||||
from the display thread to the DSF dispatch thread, whenever it is
|
||||
called by the table viewer, as in the example below:<br>
|
||||
<pre> public void updateElement(final int index) {<br> assert fTableViewer != null;<br> if (fDataProvider == null) return;<br><br> fDataProvider.getExecutor().execute(<br> new Runnable() { public void run() {<br> // Must check again, in case disposed while redispatching.<br> if (fDataProvider == null) return;<br> <br> queryItemData(index);<br> }});<br> }<br></pre>
|
||||
Likewise, when the content provider calls the table viewer, it also has
|
||||
to switch back into the display thread as in following example, when
|
||||
the content provider receives an event from the data provider, that an
|
||||
item value has changed.<br>
|
||||
<pre> public void dataChanged(final Set<Integer> indexes) {<br> // Check for dispose.<br> if (fDataProvider == null) return;<br><br> // Clear changed items in table viewer.<br> if (fTableViewer != null) {<br> final TableViewer tableViewer = fTableViewer;<br> tableViewer.getTable().getDisplay().asyncExec(<br> new Runnable() { public void run() {<br> // Check again if table wasn't disposed when <br> // switching to the display thread.<br> if (tableViewer.getTable().isDisposed()) return; // disposed<br> for (Integer index : indexes) {<br> tableViewer.clear(index);<br> }<br> }});<br> }<br> }<br></pre>
|
||||
All of this switching back and forth between threads makes the code
|
||||
look a lot more complicated than it really is, and it takes some
|
||||
getting used to, but this is the price to be paid for multi-threading.
|
||||
Whether the participants use semaphores or the dispatch thread, the
|
||||
logic is equally complicated, and we believe that using a single
|
||||
dispatch thread, makes the synchronization very explicit and thus less
|
||||
error-prone.<br>
|
||||
<p><span style="text-decoration: underline;">Data Provider Service</span></p>
|
||||
<p>The data provider service interface, <span
|
||||
style="font-family: monospace;">DataProvider</span>, is very similar
|
||||
to that of the lazy content provider. It has methods to: </p>
|
||||
<ul>
|
||||
<li>get item count</li>
|
||||
<li>get a value for given item</li>
|
||||
<li>register as listener for changes in data count and data values</li>
|
||||
</ul>
|
||||
But this is a DSF interface, and all methods must be called on the
|
||||
service's dispatch thread. For this reason, the <span
|
||||
style="font-family: monospace;">DataProvider </span>interface returns
|
||||
an instance of <span style="font-family: monospace;">DsfExecutor</span>,
|
||||
which must be used with the interface.<br>
|
||||
<p><span style="text-decoration: underline;">Slow Data Provider</span></p>
|
||||
<p>The data provider is actually implemented as a thread which is an
|
||||
inner class of <span style="font-family: monospace;">SlowDataProvider</span>
|
||||
service. The provider thread
|
||||
communicates with the service by reading Request objects from a shared
|
||||
queue, and by posting Runnable objects directly to the <span
|
||||
style="font-family: monospace;">DsfExecutor</span> but
|
||||
with a simulated transmission delay. Separately, an additional
|
||||
flag is also used to control the shutdown of the provider thread.</p>
|
||||
To simulate a real back end, the data provider randomly invalidates a
|
||||
set of items and notifies the listeners to update themselves. It
|
||||
also periodically invalidates the whole table and forces the clients to
|
||||
requery all items.<br>
|
||||
<h4>Data and Control Flow<br>
|
||||
</h4>
|
||||
This can be described in following steps:<br>
|
||||
<ol>
|
||||
<li>The table viewer requests data for an item at a given index (<span
|
||||
style="font-family: monospace;">SlowDataProviderContentProvider.updateElement</span>).<br>
|
||||
</li>
|
||||
<li>The table viewer's content provider executes a <span
|
||||
style="font-family: monospace;">Runnable </span>in the DSF
|
||||
dispatch thread and calls the data provider interface (<span
|
||||
style="font-family: monospace;">SlowDataProviderContentProvider.queryItemData</span>).</li>
|
||||
<li>Data provider service creates a Request object, and files it in a
|
||||
queue (<span style="font-family: monospace;">SlowDataProvider.getItem</span>).</li>
|
||||
<li>Data provider thread de-queues the Request object and acts on it,
|
||||
calculating the value (<span style="font-family: monospace;">ProviderThread.processItemRequest</span>).</li>
|
||||
<li>Data provider thread schedules the calculation result to be
|
||||
posted with DSF executor (<span style="font-family: monospace;">SlowDataProvider.java:185</span>).</li>
|
||||
<li>The RequestMonitor callback sets the result data in the table
|
||||
viewer (<span style="font-family: monospace;">SlowDataProviderContentProvider.java:167</span>).<br>
|
||||
</li>
|
||||
</ol>
|
||||
<h4>Running the example and full sources</h4>
|
||||
This example is implemented in the <span
|
||||
style="font-family: monospace;">org.eclipse.dd.examples.dsf</span>
|
||||
plugin, in the <span style="font-family: monospace;">org.eclipse.dd.examples.dsf.concurrent</span>
|
||||
package. <br>
|
||||
<br>
|
||||
To run the example:<br>
|
||||
<ol>
|
||||
<li>Build the test plugin (along with the <span
|
||||
style="font-family: monospace;">org.eclipse.dsdp.DSF plugin</span>)
|
||||
and launch the PDE. <br>
|
||||
</li>
|
||||
<li>Make sure to add the <span style="font-style: italic;">DSF
|
||||
Tests</span> action set to your current perspective.</li>
|
||||
<li>From the main menu, select <span style="font-style: italic;">DSF
|
||||
Tests -> Slow Data Provider</span>.</li>
|
||||
<li>A dialog will open and after a delay it will populate with data.</li>
|
||||
<li>Scroll and resize dialog and observe the update behavior.</li>
|
||||
</ol>
|
||||
<h4>Initial Notes<br>
|
||||
</h4>
|
||||
This example is supposed to be representative of a typical embedded
|
||||
debugger design problem. Embedded debuggers are often slow in
|
||||
retrieving and processing data, and can sometimes be accessed through a
|
||||
relatively slow data channel, such as serial port or JTAG
|
||||
connection. But as such, this basic example presents a couple
|
||||
of major usability problems<br>
|
||||
<ol>
|
||||
<li>The data provider service interface mirrors the table's content
|
||||
provider interface, in that it has a method to retrieve a single piece
|
||||
of data at a time. The result of this is visible to the user as
|
||||
lines of data are filled in one-by-one in the table. However,
|
||||
most debugger back ends are in fact capable of retrieving data in
|
||||
batches and are much more efficient at it than retrieving data items
|
||||
one-by-one.</li>
|
||||
<li>When scrolling quickly through the table, the requests are
|
||||
generated by the table viewer for items which are quickly scrolled out
|
||||
of view, but the service still queues them up and calculates them in
|
||||
the order they were received. As a result, it takes a very long
|
||||
time for the table to be populated with data at the location where the
|
||||
user is looking. <br>
|
||||
</li>
|
||||
</ol>
|
||||
These two problems are very common in creating UI for embedded
|
||||
debugging, and there are common patterns which can be used to solve
|
||||
these problems in DSF services. <br>
|
||||
<h3>Coalescing</h3>
|
||||
Coalescing many single-item requests into fewer multi-item requests is
|
||||
the surest way to improve performance in communication with a remote
|
||||
debugger, although it's not necessarily the simplest. There are
|
||||
two basic patterns in which coalescing is achieved:<br>
|
||||
<ol>
|
||||
<li>The back end provides an interface for retrieving data in large
|
||||
chunks. So when the service implementation receives a request for
|
||||
a single item, it retrieves a whole chunk of data, returns the single
|
||||
item, and stores the rest of the data in a local cache.</li>
|
||||
<li>The back end providers an interface for retrieving data in
|
||||
variable size chunks. When the service implementation receives a
|
||||
request for a single item, it buffers the request, and waits for other
|
||||
requests to come in. After a delay, the service clears the buffer
|
||||
and submits a request for the combined items to the data provider.</li>
|
||||
</ol>
|
||||
In practice, a combination of the two patterns is needed, but for
|
||||
purpose of an example, we implemented the second pattern in the
|
||||
"Input-Coalescing Slow Data Provider" (<span
|
||||
style="font-family: monospace;">InputCoalescingSlowDataProvider.java</span>).
|
||||
<br>
|
||||
<p><span style="text-decoration: underline;">Input Buffer</span></p>
|
||||
<p>The main feature of this pattern is a buffer for holding the
|
||||
requests before sending them to the data provider. In this
|
||||
example the user requests are buffered in two arrays: <span
|
||||
style="font-family: monospace;">fGetItemIndexesBuffer</span> and <span
|
||||
style="font-family: monospace;">fGetItemRequestMonitorsBuffer. </span>The
|
||||
<span style="font-family: monospace;">DataProvider.getItem()</span>
|
||||
implementation is changed as follows:</p>
|
||||
<pre> public void getItem(final int index, final DataRequestMonitor<String> rm) {<br> // Schedule a buffer-servicing call, if one is needed.<br> if (<span
|
||||
style="font-family: monospace;">fGetItemRequestMonitorsBuffer</span>.isEmpty()) {<br> fExecutor.schedule(<br> new Runnable() { public void run() {<br> fileBufferedRequests();<br> }},<br> COALESCING_DELAY_TIME, <br> TimeUnit.MILLISECONDS);<br> }<br> <br> // Add the call data to the buffer. <br> // Note: it doesn't matter that the items were added to the buffer <br> // after the buffer-servicing request was scheduled. This is because<br> // the buffers are guaranteed not to be modified until this dispatch<br> // cycle is over.<br> fGetItemIndexesBuffer.add(index);<br> <span
|
||||
style="font-family: monospace;">fGetItemRequestMonitorsBuffer</span>.add(rm);<br> } <br><br></pre>
|
||||
And method that services the buffer looks like this:<br>
|
||||
<pre> public void fileBufferedRequests() { <br> // Remove a number of getItem() calls from the buffer, and combine them<br> // into a request.<br> int numToCoalesce = Math.min(fGetItemIndexesBuffer.size(), COALESCING_COUNT_LIMIT);<br> final ItemRequest request = new ItemRequest(new Integer[numToCoalesce], new DataRequestMonitor[numToCoalesce]); <br> for (int i = 0; i < numToCoalesce; i++) {<br> request.fIndexes[i] = fGetItemIndexesBuffer.remove(0);<br> request.fDones[i] = <span
|
||||
style="font-family: monospace;">fGetItemRequestMonitorsBuffer</span>.remove(0);<br> }<br><br> // Queue the coalesced request, with the appropriate transmission delay.<br> fQueue.add(request);<br> <br> // If there are still calls left in the buffer, execute another <br> // buffer-servicing call, but without any delay.<br> if (!fGetItemIndexesBuffer.isEmpty()) {<br> fExecutor.execute(new Runnable() { public void run() {<br> fileBufferedRequests();<br> }});<br> }<br> }<br></pre>
|
||||
The most interesting feature of this implementation is the fact that
|
||||
there are no semaphores anywhere to control access to the input
|
||||
buffers. Even though the buffers are serviced with a delay and
|
||||
multiple clients can call the <span style="font-family: monospace;">getItem()</span>
|
||||
method, the use of a single
|
||||
dispatch thread prevents any race conditions that could corrupt the
|
||||
buffer data. In real-world implementations, the buffers and
|
||||
caches that need to be used are far more sophisticated with much more
|
||||
complicated logic, and this is where managing access to them using the
|
||||
dispatch thread is ever more important.<br>
|
||||
<h3>Cancellability</h3>
|
||||
<p><span style="text-decoration: underline;">Table Viewer</span></p>
|
||||
<p><span style="text-decoration: underline;"></span></p>
|
||||
Unlike coalescing, which can be implemented entirely within the
|
||||
service, cancellability requires that the client be modified as well
|
||||
to take advantage of this capability. For the table viewer
|
||||
content provider, this means that additional features have to be
|
||||
added. In <span style="font-family: monospace;">CancellingSlowDataProviderContentProvider.java</span>
|
||||
<span style="font-family: monospace;">ILazyContentProvider.updateElement()</span>
|
||||
was changes as follows:<br>
|
||||
<pre> public void updateElement(final int index) {<br> assert fTableViewer != null;<br> if (fDataProvider == null) return;<br> <br> // Calculate the visible index range.<br> final int topIdx = fTableViewer.getTable().getTopIndex();<br> final int botIdx = topIdx + getVisibleItemCount(topIdx);<br> <br> fCancelCallsPending.incrementAndGet();<br> fDataProvider.getExecutor().execute(<br> new Runnable() { public void run() {<br> // Must check again, in case disposed while redispatching.<br> if (fDataProvider == null || fTableViewer.getTable().isDisposed()) return;<br> if (index >= topIdx && index <= botIdx) {<br> queryItemData(index);<br> }<br> cancelStaleRequests(topIdx, botIdx);<br> }});<br> }<br></pre>
|
||||
Now the client keeps track of the requests it made to the service in <span
|
||||
style="font-family: monospace;">fItemDataDones</span>, and above, <span
|
||||
style="font-family: monospace;">cancelStaleRequests()</span> iterates
|
||||
through all the outstanding requests and cancels the ones that are no
|
||||
longer in the visible range.<br>
|
||||
<p><span style="text-decoration: underline;">Data Provider Service<span
|
||||
style="text-decoration: underline;"></span></span></p>
|
||||
<p><span style="text-decoration: underline;"><span
|
||||
style="text-decoration: underline;"></span></span></p>
|
||||
<p>The data provider implementation
|
||||
(<span style="font-family: monospace;">CancellableInputCoalescingSlowDataProvider.java</span>),
|
||||
builds on top of the
|
||||
coalescing data provider. To make the canceling feature useful,
|
||||
the data provider service has to limit the size of the request
|
||||
queue. This is because in this example which simulates
|
||||
communication with a target and once requests are filed into the
|
||||
request
|
||||
queue, they cannot be canceled, just like a client can't cancel
|
||||
request once it sends them over a socket. So instead, if a flood
|
||||
of <span style="font-family: monospace;">getItem()</span>
|
||||
calls comes in, the service has to hold most of them in the coalescing
|
||||
buffer in case the client decides to cancel them. Therefore the
|
||||
<span style="font-family: monospace;">fileBufferedRequests()</span>
|
||||
method includes a simple check before servicing
|
||||
the buffer, and if the request queue is full, the buffer servicing call
|
||||
is delayed.</p>
|
||||
<pre> if (fQueue.size() >= REQUEST_QUEUE_SIZE_LIMIT) {<br> if (fGetItemIndexesBuffer.isEmpty()) {<br> fExecutor.schedule(<br> new Runnable() { public void run() {<br> fileBufferedRequests();<br> }},<br> REQUEST_BUFFER_FULL_RETRY_DELAY, <br> TimeUnit.MILLISECONDS);<br> }<br> return;<br> } <br></pre>
|
||||
Beyond this change, the only other significant change is that before
|
||||
the requests are queued, they are checked for cancellation.<br>
|
||||
<h3>Final Notes<br>
|
||||
</h3>
|
||||
The example given here is fairly simplistic, and chances are that the
|
||||
same example could be implemented using semaphores and free threading
|
||||
with perhaps fewer lines of code. But what we have found is that
|
||||
as the problem gets bigger, the amount of
|
||||
features in the data provider increases, the state of the
|
||||
communication protocol gets more complicated, and the number of modules
|
||||
needed in the service layer increases, using free threading and
|
||||
semaphores does not safely scale. Using a dispatch thread for
|
||||
synchronization certainly doesn't make the inherent problems of the
|
||||
system less complicated, but it does help eliminate the race conditions
|
||||
and deadlocks from the overall system.<br>
|
||||
<p>Coalescing and Cancellability are both optimizations. Neither
|
||||
of these optimizations affected the original interface of the service,
|
||||
and one of them only needed a service-side modification. But as
|
||||
with all optimizations, it is often better to first make sure that the
|
||||
whole system is working correctly and then add optimizations where they
|
||||
can make the biggest difference in user experience. </p>
|
||||
<p>The above examples of optimizations can take many forms, and as
|
||||
mentioned with coalescing, caching data that is retrieved from the data
|
||||
provider is the most common form of data coalescing. For
|
||||
cancellation, many services in DSF build on top of other services,
|
||||
which means that even a low-level service can cause a higher
|
||||
level service to retrieve data, while another event might cause it to
|
||||
cancel those requests. The perfect example of this is a Variables
|
||||
service, which is responsible for calculating the value of expressions
|
||||
shown in the Variables view. The Variables service reacts to the
|
||||
Run Control service, which issues a suspended event and then requests a
|
||||
set of variables to be evaluated by the debugger back end. But as
|
||||
soon as a resumed event is issued by Run Control, the Variables service
|
||||
needs to cancel the pending evaluation requests.<br>
|
||||
</p>
|
||||
<br>
|
||||
<br>
|
||||
</body>
|
||||
</html>
|
|
@ -39,6 +39,11 @@ public class AsyncHelloWorld {
|
|||
System.out.println("Hello world");
|
||||
//#ifdef excercises
|
||||
// TODO Exercise 1: - Call the second async. "Hello world 2" method.
|
||||
// Hint: Calling an asynchronous method requires passing to it a
|
||||
// request monitor. A new request monitor can be constructed with
|
||||
// a parent RequestMonitor as an argument argument. The parent gets
|
||||
// completed automatically when the lower level request monitor is
|
||||
// completed.
|
||||
rm.done();
|
||||
//#else
|
||||
//# RequestMonitor rm2 = new RequestMonitor(ImmediateExecutor.getInstance(), rm);
|
||||
|
|
|
@ -52,7 +52,10 @@ public class AsyncQuicksort {
|
|||
if (right > left) {
|
||||
int pivot = left;
|
||||
//#ifdef excercises
|
||||
// TODO: Request Monitors Exercise 2 - Convert partition to an async. method.
|
||||
// TODO: Exercise 2 - Convert the call to partition into an
|
||||
// asynchronous call to asyncPartition().
|
||||
// Hint: The rest of the code below should be executed inside
|
||||
// the DataRequestMonitor.handleCompleted() overriding method.
|
||||
int newPivot = partition(array, left, right, pivot);
|
||||
printArray(array, left, right, newPivot);
|
||||
|
||||
|
@ -76,7 +79,6 @@ public class AsyncQuicksort {
|
|||
//# }
|
||||
//# });
|
||||
//#endif
|
||||
|
||||
} else {
|
||||
rm.done();
|
||||
}
|
||||
|
@ -84,6 +86,8 @@ public class AsyncQuicksort {
|
|||
|
||||
//#ifdef excercises
|
||||
// TODO Exercise 2 - Convert partition to an asynchronous method.
|
||||
// Hint: a DataRequestMonitor<Integer> should be used to carry the
|
||||
// return value to the caller.
|
||||
static int partition(int[] array, int left, int right, int pivot)
|
||||
//#else
|
||||
//# static void asyncPartition(int[] array, int left, int right, int pivot, DataRequestMonitor<Integer> rm)
|
||||
|
@ -105,11 +109,12 @@ public class AsyncQuicksort {
|
|||
array[store] = pivotValue;
|
||||
|
||||
//#ifdef excercises
|
||||
// TODO: Request Monitors Exercise 2 - Convert partition to an async. method.
|
||||
// TODO: Request Monitors Exercise 2 - Return the data to caller using
|
||||
// a request monitor.
|
||||
return store;
|
||||
//#else
|
||||
//# rm.setData(store);
|
||||
//# rm.done();
|
||||
//# rm.setData(store);
|
||||
//# rm.done();
|
||||
//#endif
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue