Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workflow never completes when waiting on multiple activities unless ToList is used #1211

Closed
stuartleeks opened this issue Dec 12, 2023 · 4 comments
Labels
kind/bug Something isn't working

Comments

@stuartleeks
Copy link

Expected Behavior

Waiting on multiple activities with Task.WhenAll should work when passing an IEnumerable.

Actual Behavior

When capturing the Tasks from multiple activity invocations, passing the tasks to Task.WhenAll works fine if the tasks are passed as a list. If the tasks are passed as an IEnumerable<Task> then the workflow never completes.

Steps to Reproduce the Problem

I have a workflow project where the set of activities to call is determined based on the payload of an incoming request. In the processing it is possible for multiple activities to be executed in parallel:

// ...

var actionTasks = step.Actions.Select(action =>
		context.CallActivityAsync<InvokeProcessorResult>(nameof(InvokeProcessorActivity), action)
	).ToList(); // if ToList is omitted, the workflow never completes
await Task.WhenAll(actionTasks);

// ...

Release Note

RELEASE NOTE: Fix awaiting multiple activity tasks works with IEnumerable

@stuartleeks stuartleeks added the kind/bug Something isn't working label Dec 12, 2023
@philliphoff
Copy link
Collaborator

@stuartleeks Do you have a repro project that you can share? I threw together a project but do not see that behavior; the workflow completes as expected once all the activities complete, whether .ToList() is used or not.

@stuartleeks
Copy link
Author

stuartleeks commented Jan 5, 2024

@philliphoff - I've created a stripped back version of the project where I hit this and pushed it here: https://github.com/stuartleeks/dapr-wf-csharp-1211

Hopefully it's easy to set up/run, but let me kjnow if you have any questions

@philliphoff
Copy link
Collaborator

Ok, so found the problem:

	await Task.WhenAll(activityTasks);

	var result = activityTasks.Select(t=>t.Result).ToArray();

...should instead be:

	var result = await Task.WhenAll(activityTasks);

In the original, activityTasks is a LINQ-based lazily-evaluated IEnumerable<Task>. It will first be evaluated by the Task.WhenAll() which causes the activities to be spawned and then awaited. Once that's done, it gets to the activityTasks.Select(t=>t.Result).ToArray(). This causes a second evaluation of the enumerable which spawns the activities again, but then tries to synchronously wait for each to finish (due to the use of Result), which is where I think the problem arises.

Task.WhenAll() returns the results of each awaited Task so that they can be used without reevaluating the original enumerable.

@stuartleeks
Copy link
Author

Ah, good spot 🤦

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants