Index: src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/UploadAction.java	(revision 13087)
+++ src/org/openstreetmap/josm/actions/UploadAction.java	(working copy)
@@ -24,8 +24,8 @@
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
 import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
 import org.openstreetmap.josm.gui.io.UploadDialog;
-import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
 import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.util.GuiHelper;
@@ -258,13 +258,12 @@
         }
 
         MainApplication.worker.execute(
-                new UploadPrimitivesTask(
+                AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(
                         UploadDialog.getUploadDialog().getUploadStrategySpecification(),
                         layer,
                         apiData,
-                        cs
-                )
-        );
+                        cs)
+                        .get());
     }
 
     @Override
Index: src/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTask.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTask.java	(nonexistent)
+++ src/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTask.java	(working copy)
@@ -0,0 +1,213 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import org.openstreetmap.josm.data.APIDataSet;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.ProgressTaskId;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.io.UploadStrategySpecification;
+import org.openstreetmap.josm.tools.I18n;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Allows to upload the primitives in background
+ */
+public class AsynchronousUploadPrimitivesTask extends UploadPrimitivesTask {
+
+    /**
+     * Static instance
+     */
+    private static AsynchronousUploadPrimitivesTask asynchronousUploadPrimitivesTask = null;
+    private static final boolean HIDE_UPLOADING_PRIMITIVES = false;
+    private static final String TRANSIENT_LAYER_NAME = "Transient upload layer";
+
+    /**
+     * Member fields
+     */
+    private final ProgressTaskId taskId;
+    private final List<OsmPrimitive> osmPrimitiveList;
+    private final OsmDataLayer uploadDataLayer;
+
+    /**
+     * Private constructor to restrict creating more Asynchronous upload tasks
+     *
+     * @param uploadStrategySpecification
+     * @param osmDataLayer
+     * @param apiDataSet
+     * @param changeset
+     */
+    private AsynchronousUploadPrimitivesTask(UploadStrategySpecification uploadStrategySpecification, OsmDataLayer osmDataLayer, APIDataSet apiDataSet, Changeset changeset) {
+        super(uploadStrategySpecification,
+                osmDataLayer,
+                apiDataSet,
+                changeset);
+
+        uploadDataLayer = osmDataLayer;
+
+        // Get the primitives that are added/deleted/edited
+        osmPrimitiveList = apiDataSet.getPrimitives();
+
+        // Create a ProgressTaskId for background upload
+        taskId = new ProgressTaskId("core", "async-upload");
+    }
+
+    /**
+     * Creates a transient OsmDataLayer with DataSet containing the primitives to be added/updated/deleted
+     *
+     * @return
+     */
+    private static OsmDataLayer createTransientOsmDataLayer(String name) {
+        DataSet dataSet = new DataSet();
+        return new OsmDataLayer(dataSet, name, null);
+    }
+
+    /**
+     * Disables the list of primitives and their referrers
+     * @param osmPrimitiveList
+     */
+    private static void disablePrimitives(List <OsmPrimitive> osmPrimitiveList) {
+        for (OsmPrimitive p : osmPrimitiveList) {
+            if(!p.isDeleted()) {
+                p.setDisabledState(HIDE_UPLOADING_PRIMITIVES);
+                for (OsmPrimitive ref : p.getReferrers()) {
+                    ref.setDisabledState(HIDE_UPLOADING_PRIMITIVES);
+                }
+            }
+        }
+    }
+
+    /**
+     * Enables the list of primitives provided
+     * @param osmPrimitiveList
+     */
+    private static void enablePrimitives(List <OsmPrimitive> osmPrimitiveList) {
+        for (OsmPrimitive p : osmPrimitiveList) {
+
+            // Only enable the primitives that are still present
+            if (!p.isDeleted()) {
+                p.unsetDisabledState();
+
+                // Enable the referrers of all primitives
+                for (OsmPrimitive ref : p.getReferrers()) {
+                    if (!ref.isDeleted())
+                        ref.unsetDisabledState();
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates an instance of AsynchronousUploadPrimitiveTask
+     *
+     * @param uploadStrategySpecification
+     * @param dataLayer
+     * @param apiDataSet
+     * @param changeset
+     * @return Optional<AsynchronousUploadPrimitivesTask>
+     */
+    public static Optional<AsynchronousUploadPrimitivesTask> createAsynchronousUploadTask
+            (UploadStrategySpecification uploadStrategySpecification,
+             OsmDataLayer dataLayer, APIDataSet apiDataSet, Changeset changeset) {
+        synchronized (AsynchronousUploadPrimitivesTask.class) {
+            if (asynchronousUploadPrimitivesTask != null) {
+                if (!GraphicsEnvironment.isHeadless()) {
+                    GuiHelper.runInEDTAndWait(() ->
+                            JOptionPane.showMessageDialog(MainApplication.parent,
+                                    I18n.tr("A background upload is already in progress. Kindly wait for it to finish before uploading new changes")));
+                }
+                return Optional.empty();
+            } else {
+                // Get a transient upload layer
+                OsmDataLayer transientLayer = createTransientOsmDataLayer(TRANSIENT_LAYER_NAME);
+                MainApplication.getLayerManager().addLayer(transientLayer);
+                MainApplication.getLayerManager().setActiveLayer(transientLayer);
+
+                // Create an asynchronous upload task with the transient layer
+                asynchronousUploadPrimitivesTask = new AsynchronousUploadPrimitivesTask(
+                        uploadStrategySpecification,
+                        dataLayer,
+                        apiDataSet,
+                        changeset);
+                return Optional.ofNullable(asynchronousUploadPrimitivesTask);
+            }
+        }
+    }
+
+    /**
+     * Get the current upload task
+     * @return Optional<AsynchronousUploadPrimitivesTask>
+     */
+    public static Optional<AsynchronousUploadPrimitivesTask> getCurrentAsynchronousUploadTask () {
+        return Optional.ofNullable(asynchronousUploadPrimitivesTask);
+    }
+
+    /**
+     * Lock the dataset
+     */
+    protected void lockDataSet () {
+        uploadDataLayer.data.beginUpdate();
+    }
+
+    /**
+     * Unlock the upload task dataset
+     */
+    protected void unlockDataSet () {
+        uploadDataLayer.data.endUpdate();
+    }
+
+    /**
+     * Merge the transient upload layer with the current edit layer
+     */
+    private void mergeTransientLayer () {
+        OsmDataLayer currentEditLayer = MainApplication.getLayerManager().getEditLayer();
+        currentEditLayer.mergeFrom(uploadDataLayer);
+        MainApplication.getLayerManager().removeLayer(uploadDataLayer);
+        currentEditLayer.rename(uploadDataLayer.getName());
+    }
+
+    @Override
+    protected void cleanupAfterUpload () {
+        // The cleanup happens on EDT. Hence unlocking the dataset
+        unlockDataSet();
+        super.cleanupAfterUpload();
+    }
+
+    @Override
+    public ProgressTaskId canRunInBackground() {
+        return taskId;
+    }
+
+    @Override
+    protected void realRun() {
+        // Lock the dataset for any changes from other threads
+        lockDataSet();
+        super.realRun();
+    }
+
+    @Override
+    protected void cancel() {
+        super.cancel();
+        asynchronousUploadPrimitivesTask = null;
+    }
+
+    @Override
+    protected void finish() {
+        try {
+            // Note: Always merge back the layers.
+            // Even in cases with conflicts/failures the users changes should not be discarded.
+            mergeTransientLayer();
+
+            super.finish();
+        } finally {
+            asynchronousUploadPrimitivesTask = null;
+        }
+    }
+}
\ No newline at end of file
Index: test/unit/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTaskTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTaskTest.java	(nonexistent)
+++ test/unit/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTaskTest.java	(working copy)
@@ -0,0 +1,102 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.junit.*;
+import org.openstreetmap.josm.data.APIDataSet;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.*;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.io.UploadStrategySpecification;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import java.util.*;
+
+public class AsynchronousUploadPrimitivesTaskTest {
+
+    private UploadStrategySpecification strategy;
+    private OsmDataLayer layer;
+    private APIDataSet toUpload;
+    private Changeset changeset;
+    private AsynchronousUploadPrimitivesTask uploadPrimitivesTask;
+
+    /**
+     * Setup tests
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules();
+
+    @Before
+    public void bootStrap() {
+        DataSet dataSet = new DataSet();
+        Node node1 = new Node();
+        Node node2 = new Node();
+        node1.setCoor(new LatLon(0, 0));
+        node2.setCoor(new LatLon(30, 30));
+        Way way = new Way();
+        way.addNode(node1);
+        way.addNode(node2);
+        dataSet.addPrimitive(node1);
+        dataSet.addPrimitive(node2);
+        dataSet.addPrimitive(way);
+
+        toUpload = new APIDataSet(dataSet);
+        layer = new OsmDataLayer(dataSet, "uploadTest", null);
+        strategy = new UploadStrategySpecification();
+        changeset = new Changeset();
+        uploadPrimitivesTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset).get();
+    }
+
+    @After
+    public void tearDown () {
+        toUpload = null;
+        layer = null;
+        strategy = null;
+        changeset = null;
+        uploadPrimitivesTask = null;
+    }
+
+    @Test
+    public void testSingleUploadInstance () {
+        Optional<AsynchronousUploadPrimitivesTask> task = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset);
+        Assert.assertFalse(task.isPresent());
+    }
+
+    @Test
+    public void testPrimitiveCannotBeAdded () throws InterruptedException {
+        Node newNode = new Node();
+        newNode.setCoor(new LatLon(1,1));
+        uploadPrimitivesTask.lockDataSet();
+        Thread otherThread = new Thread(() -> layer.data.addPrimitive(newNode));
+
+        otherThread.start();
+        otherThread.join(1000);
+        Assert.assertFalse(layer.data.containsNode(newNode));
+        uploadPrimitivesTask.unlockDataSet();
+    }
+
+    @Test
+    public void testPrimitiveCannotBeEdited () throws InterruptedException {
+        Way way = layer.data.getWays().iterator().next();
+        uploadPrimitivesTask.lockDataSet();
+        Thread otherThread = new Thread(() -> way.put("test-key", "test-value"));
+
+        otherThread.start();
+        otherThread.join(1000);
+        Assert.assertNull(way.get("test-key"));
+        uploadPrimitivesTask.unlockDataSet();
+    }
+
+    @Test
+    public void testPrimitiveCannotBeDeleted () throws InterruptedException {
+        Node node1 = layer.data.getNodes().iterator().next();
+        uploadPrimitivesTask.lockDataSet();
+        Thread otherThread = new Thread(() -> layer.data.removePrimitive(node1.getPrimitiveId()));
+
+        otherThread.start();
+        otherThread.join(1000);
+        Assert.assertTrue(layer.data.containsNode(node1));
+        uploadPrimitivesTask.unlockDataSet();
+    }
+}
