Repository: PrefectHQ/prefect

Issue:
LocalDaskExecutor with "processes" scheduler runs some tasks twice
## Description
I noticed that when running a Flow containing mapped tasks via the `LocalDaskExecutor(scheduler="processes")` some of the tasks in the flow run twice, and in worst case scenario the whole pipeline runs twice.

With all the other executor setups (e.g. `DaskExecutor` or just using `threads` scheduler in the `LocalDaskExecutor`( the pipeline runs correctly.

## Expected Behavior
The execution of the same pipeline should be the same across different executors.



## Reproduction
I was only able to create this simple example which shows that one of the tasks ran twice. However, the original pipeline that has a similar structure runs all its tasks twice, every time.

```[Python]
@task
def check_files():
    print("CHECKING FILES")
    return True

@task
def load_files(checked):
    return [1,2]

@task
def process_files(f):
    return f

@task
def agg(f):
    return sum(f)

with Flow("EvalPipeline") as flow:
    checked = check_files()
    files = load_files(checked)
    processed = process_files.map(files)
    summary = agg(processed)
state = flow.run(
    executor=LocalDaskExecutor(scheduler="processes", num_workers=1)
)
```

Output:
![example_output](https://user-images.githubusercontent.com/1793977/89765052-dbeff400-daf5-11ea-8757-618489d7d1d1.png)


## Environment
* prefect==0.12.6
* dask==2.15.0



Discussion:

Resolving patch:
diff --git a/src/prefect/engine/executors/dask.py b/src/prefect/engine/executors/dask.py
--- a/src/prefect/engine/executors/dask.py
+++ b/src/prefect/engine/executors/dask.py
@@ -535,4 +535,17 @@ def wait(self, futures: Any) -> Any:
         # import dask here to reduce prefect import times
         import dask
 
-        return dask.compute(futures, scheduler=self.scheduler, pool=self._pool)[0]
+        # dask's multiprocessing scheduler hardcodes task fusion in a way
+        # that's not exposed via a `compute` kwarg. Until that's fixed, we
+        # disable fusion globally for the multiprocessing scheduler only.
+        # Since multiprocessing tasks execute in a remote process, this
+        # shouldn't affect user code.
+        if self.scheduler == "processes":
+            config = {"optimization.fuse.active": False}
+        else:
+            config = {}
+
+        with dask.config.set(config):
+            return dask.compute(
+                futures, scheduler=self.scheduler, pool=self._pool, optimize_graph=False
+            )[0]


Test patch:
diff --git a/tests/engine/executors/test_executors.py b/tests/engine/executors/test_executors.py
--- a/tests/engine/executors/test_executors.py
+++ b/tests/engine/executors/test_executors.py
@@ -107,27 +107,25 @@ def test_submit_sets_task_name(self):
             assert f.key.startswith("inc-1-")
             assert res == 2
 
-    def test_only_compute_once(self):
-        e = LocalDaskExecutor()
-        count = 0
+    @pytest.mark.parametrize("scheduler", ["threads", "processes", "synchronous"])
+    def test_only_compute_once(self, scheduler, tmpdir):
+        e = LocalDaskExecutor(scheduler)
 
-        def inc(x):
-            nonlocal count
-            count += 1
+        def inc(x, path):
+            if os.path.exists(path):
+                raise ValueError("Should only run once!")
+            with open(path, "wb"):
+                pass
             return x + 1
 
         with e.start():
-            f1 = e.submit(inc, 0)
-            f2 = e.submit(inc, f1)
-            f3 = e.submit(inc, f2)
+            f1 = e.submit(inc, 0, str(tmpdir.join("f1")))
+            f2 = e.submit(inc, f1, str(tmpdir.join("f2")))
+            f3 = e.submit(inc, f2, str(tmpdir.join("f3")))
             assert e.wait([f1]) == [1]
-            assert count == 1
             assert e.wait([f2]) == [2]
-            assert count == 2
             assert e.wait([f3]) == [3]
-            assert count == 3
             assert e.wait([f1, f2, f3]) == [1, 2, 3]
-            assert count == 3
 
     def test_is_pickleable(self):
         e = LocalDaskExecutor()