Allow parallel execution of tasks #3534
Replies: 31 comments 1 reply
-
I'd piggy back on this and also (re?)mention the idea of async tasks. |
Beta Was this translation helpful? Give feedback.
-
Took a stab at this with this spike. Not ideal, but at least it cut down our 8-minute build time down to five. |
Beta Was this translation helpful? Give feedback.
-
A big issue with parallelism is viewing the log output. I came up with a solution that buffers output so that the log looks exactly like the tasks are executed sequentially, but they are in fact simultaneous. As soon as the first action is finished, all the second action's buffered output is dumped immediately and you get to watch the second action continue live- or, if it's also finished, you see the third. Etc. This can be used in your build scripts without modifying Cake. https://gist.github.com/jnm2/a8a39b67a584ad555360102407049ae2 Task("Example")
.Does(() => RunSimultaneously(
() =>
{
StartProcess("ping", new ProcessSettings().WithArguments(a => a.Append("github.com")));
Information("FINISHED 1");
},
() =>
{
RunSimultaneously(() =>
{
StartProcess("ping", new ProcessSettings().WithArguments(a => a.Append("192.123.45.67")));
Information("FINISHED 2");
},
() =>
{
StartProcess("ping", new ProcessSettings().WithArguments(a => a.Append("twitter.com")));
Information("FINISHED 3");
});
}
)); I'd love for some of this to be baked into Cake's dependency graph analysis without even needing to manually specify RunSimultaneously. That would be fantastic! |
Beta Was this translation helpful? Give feedback.
-
might i suggest this for declaring parallel taks. Feel free to ignore if you think it doesn't make sense:
|
Beta Was this translation helpful? Give feedback.
-
I feel like it should be during the dependency graph analysis that it should determine how to best spin off and schedule tasks on their own threads. I feel that introducing any special syntax is unnecessary, as the same task definition should would regardless of parallelization. @jnm2 As soon as I dropped my parallelization module into my project my build logs were almost useless. I think I'll take a cue from your gist and incorporate the output capturing into a buffered console writer. Still pretty satisfied with the gains, though my dependency graph analysis algorithm could definitely use some work. |
Beta Was this translation helpful? Give feedback.
-
Okay so how about this use case: I want to run unit tests and integration tests before building the installer, but both take a while. So what I'd like to do run tests and build the installer at the same time, but cancel building the installer as soon as any test fails. If the installer task finishes before the tests, it should wait for the tests to complete (or fail) before staging the build installer files. |
Beta Was this translation helpful? Give feedback.
-
In pseudocode: var runUnitTests = Task("RunUnitTests").Does(() => ...);
var integrationTestNewDatabase = Task("IntegrationTestNewDatabase").Does(() => ...);
var integrationTestUpgradeDatabase = Task("IntegrationTestUpgradeDatabase").Does(() => ...);
var buildInstaller = Task("BuildInstaller").Does(parallelDependencies =>
{
BuildInstallerStep1();
if (parallelDependencies.HaveFailure) return; // If any fails, let the build end
BuildInstallerStep2();
if (parallelDependencies.HaveFailure) return;
BuildInstallerStep3();
if (!parallelDependencies.WaitForSuccess()) return; // Waits for all to succeed or any to fail
StageFiles();
});
Task("TestAndBuildInstaller")
.DependsOn(runUnitTests)
.DependsOn(integrationTestNewDatabase)
.DependsOn(integrationTestUpgradeDatabase)
.DependsOn(buildInstaller, parallelDependences: new[] {
runUnitTests,
integrationTestNewDatabase,
integrationTestUpgradeDatabase
}); |
Beta Was this translation helpful? Give feedback.
-
Task("stage-installer")
.IsDependentOn("unit-tests")
.IsDependentOn("integration-tests")
.IsDependentOn("build-installer");
Task("ci")
.IsDependentOn("unit-tests")
.IsDependentOn("integration-tests")
.IsDependentOn("build-installer")
.IsDependentOn("stage-installer"); We have a similar setup on the project I dropped my module in. |
Beta Was this translation helpful? Give feedback.
-
@aabenoja Much cleaner! However, no early exit for It's a little weird getting my head around the fact that Tasks would execute in two stages: 1. |
Beta Was this translation helpful? Give feedback.
-
Actually, Task("unit-tests")
.IgnoreCancellation();
Task("integration-tests")
.IgnoreCancellation(); While maybe not ideal, but at least this way the whole build will still fail and we get the full results of these two tasks. It makes sense to me that |
Beta Was this translation helpful? Give feedback.
-
I'm thinking cancellation should be opt-in.
So along those lines, it makes even more sense to me if the dependencies are not parallel unless opted in. Seems the safest approach. |
Beta Was this translation helpful? Give feedback.
-
Which I am. I've been generating task chains for each dependency, sharing tasks where necessary, and passing around a single CancellationToken across all tasks.
Task("nuget-restore").Does(() => {});
Task("compile")
.IsDependentOn("nuget-restore")
.Does(() => {});
Task("unit-tests")
.IsDependentOn("compile")
.Does(() => {});
Task("integration-tests")
.IsDependentOn("compile")
.Does(() => {});
Task("tests")
.IsDependentOn("unit-tests")
.IsDependentOn("integration-tests"); To go more in-depth on what I've done, The whole idea is to execute in parallel where it makes sense. It's possible to throw |
Beta Was this translation helpful? Give feedback.
-
Another example is a mvc web app. Before we create a deployable zip to drop into iis we want to ensure javascript is compiled, javascript tests are passing, our dlls are generated, and xunit tests (both unit and integration) are run. We shouldn't care what order these things and their dependencies occur, just that they happen, no different than when setting up the task without parallelization in mind. If anything, it makes more sense to me to use a cli flag run all tasks sequentially or in parallel. Having a weird mix of parallel and not parallel tasks seems like there's an issue with what is understood as a task's "dependencies." |
Beta Was this translation helpful? Give feedback.
-
That's certainly an opinionated way for Cake to go, but it might be a good thing. |
Beta Was this translation helpful? Give feedback.
-
Counterexample: Task("Clean")
.Does(() => { });
// Priority #1: You want to be able to restore without cleaning
Task("Restore")
.Does(() => { });
// Priority #2: You want to be able to build without cleaning or restoring
Task("Build")
.Does(() => { });
// Priority #3: But never pack without cleaning
// Problem: how do you keep all three dependencies from attempting to run at the same time while preserving priorities 1 and 2?
Task("Pack")
.IsDependentOn("Clean")
.IsDependentOn("Restore")
.IsDependentOn("Build")
.Does(() => { }); |
Beta Was this translation helpful? Give feedback.
-
So we need to be able to say:
|
Beta Was this translation helpful? Give feedback.
-
Do we want to specify a potentially different parallel relationship between dependencies every time they show up in dependent tasks? It could lead to duplication and inconsistency, but the API is minimalist and perhaps the extra control per dependent over the parallelism of the dependencies will be needed someday: Task("Build")
.Does(() => { });
Task("TestA")
.IsDependentOn("Build")
.Does(() => { });
Task("TestB")
.IsDependentOn("Build")
.Does(() => { });
Task("BuildInstaller")
.IsDependentOn("Build")
.Does(() => { });
Task("AllTests")
.IsDependentOnParallel("TestA", "TestB");
Task("TestAndBuildInstaller")
.IsDependentOnParallel("AllTests", "BuildInstaller"); |
Beta Was this translation helpful? Give feedback.
-
So in this API, the order of the Task("Pack")
.IsDependentOn("Clean")
.IsDependentOn("Restore")
.IsDependentOn("Build")
.IsDependentOnParallel("X", "Y", "Z")
.Does(() => { }); |
Beta Was this translation helpful? Give feedback.
-
@patriksvensson I'm interested in your thoughts. |
Beta Was this translation helpful? Give feedback.
-
Another reason not to parallelize by default is that it messes with logs, and there is a solution for that but it means redirecting the standard output and error of everything you run, losing Windows console colors. |
Beta Was this translation helpful? Give feedback.
-
Personally, I would prefer any sort of parallelization be opt-in as you suggest. There are just too many build scripts out there to turn on something that so fundamentally changes flow without causing major headaches. As an alternative to your API, what if we used an
It's a little more verbose, but to me it's clearer what's going on and better matches the existing API. |
Beta Was this translation helpful? Give feedback.
-
Cool! That's a new angle.
|
Beta Was this translation helpful? Give feedback.
-
Any progress on this topic? I'd like to have such a feature to speed up large project builds. |
Beta Was this translation helpful? Give feedback.
-
I have used https://cakebuild.net/docs/fundamentals/asynchronous-tasks and RunTarget to get this working now. It isn't pretty but does allow multiple chains of tasks to be ran. If there was a nice way for logs that would be even better. Task("__SplitHere")
.Does(async () => {
var dotNetTasks = System.Threading.Tasks.Task.Run(() => RunTarget("__DotNetTasks"));
var clientTasks = System.Threading.Tasks.Task.Run(() => RunTarget("__ClientTasks"));
await System.Threading.Tasks.Task.WhenAll(dotNetTasks, clientTasks);
}); |
Beta Was this translation helpful? Give feedback.
-
@andymac4182 Here's an interim solution for the log issue that might interest you: #156 (comment) |
Beta Was this translation helpful? Give feedback.
-
I pretty much liked the suggestion of @andymac4182 to use RunTarget. I tried it out and It works fine with the exception that each call to
Everything invoked through |
Beta Was this translation helpful? Give feedback.
-
I am currently in the process of trying to run our tasks in parallel as well and also hitting several issues. I did try to use the solution mentioned in #156 (comment) and this does mostly work, although we ran into issues with the Cake.BuildSystems.Module that reported duplicate tasks, which probably has something to do with the fact that RunTarget will just start a new run.
So would it be an idea to add a
This RunTask is mainly different from RunTarget that it wouldn't create a complete new context and doesn't show an output itself, but rather it will kind of manipulate the list of tasks that have to be run. The output of the task in the example above would look like this:
|
Beta Was this translation helpful? Give feedback.
-
Any new thoughts on "how to run tasks in parallel"? Would be awesome to get a statement or roadmap for future Cake evolution. |
Beta Was this translation helpful? Give feedback.
-
I like the suggestion As a developer who is looking to use Cake: I am disappointed that this idea has not been implemented. In my case: I have a bunch of pre-build setup steps that can be executed in parallel, and I would like parallel execution to save time, where each step is a cake Task (so that I don't need to codify shenanigans). In regards to the difficulty of organizing console output when multiple tasks are running in parallel, the suggestion above sounds nice: #3534 (comment) |
Beta Was this translation helpful? Give feedback.
-
I guess we are not getting the parallel tasks right? 😭 |
Beta Was this translation helpful? Give feedback.
-
Image the following task graph which start at
1
and ends at5
:Normally, we would do a topographic sort that gives us the order
1 -> 2 -> 3 -> 4 -> 5
, but since this example graph is diamond shaped, we could actually run task2
,3
and4
in parallel, by treating them as a single task which would result in the execution order1 -> (2 | 3 | 4) -> 5
.Since most tasks in a Cake script probably will be I/O bound, this isn't always what you would want, but theoretically this might shave some seconds of a big build script if applied to the right kind of tasks. We could make this functionality opt-in to prevent it from actually being slower.
Beta Was this translation helpful? Give feedback.
All reactions