r/csharp 1d ago

Blog Svelto.Tasks 2.0: Efficient Multi-threaded Task Management for C#

I am back writing articles on my blog and I start with something LONG OVERDUE!

After years quietly powering several games I made, I’m finally presenting Svelto.Tasks 2.0.

Svelto.Tasks is an engine-agnostic, multithreaded and zero allocation task runner for C#, born from the need to have a game centric tasks framework with features that .NET tasks couldn't provide. Its premise is simple:

Tasks are iterator blocks. Runners are schedulers. Rather than handing execution timing and context to the runtime, a runner lets you decide:

- when a task advances,
- where it runs — main thread or a dedicated worker,
- how much work happens per tick,
- and when an entire task context must stop.

That last point matters a lot in games: for example, when a match ends, I want every task belonging to that match to stop with it—not rely on a CancellationToken having been passed and checked correctly through every layer.

Svelto.Tasks supports lightweight coroutines, composable tasks with return values and continuations, background runners, bounded parallel batches, pooling, profiling hooks, and optional Unity/Burst integrations.

It also interoperates with async/await, although the .NET Task bridge and Unity Burst job path are currently experimental and need real-world feedback.

It is not a replacement for Unity Jobs—but it provides a useful alternative for workloads where controlling execution, lifetime, pacing, and profiling is the priority.

The repository includes runnable .NET examples, tests, benchmarks

🔗 https://github.com/sebas77/Svelto.Tasks

also the repository

🔗https://github.com/sebas77/Svelto.Tasks.Examples

shows how it can be used to simplify massive parallelism synchronization. The example itself is actually quite interesting also because it uses the new compute buffer Unity api BeginWrite/EndWrite to upload compute buffers and graphic fences for cpu/gpu synchronization.

The article is at

🔗 https://www.sebaslab.com/svelto-tasks-2-0-efficient-multi-threaded-task-management-for-csharp/

Have fun! (and feedback is welcome, also on my discord server: https://discord.com/invite/uuTCYewjtc)

0 Upvotes

25 comments sorted by

11

u/fruediger 23h ago

Hi, I'm going to be honest with you: Your repo seems way to big and complex for me quickly skim through it and examine your code in detail. From what I saw, at least part of it looks like it is AI-generated. Also it seems to be structured in some special way, I believe that's Unity-specific, which is why I had a hard time trying to navigate it.

Please let me tell you a little anecdote which might be related to the claims you make about your project, one claim in particular:\ You see, a few weeks ago some posted a project of theirs here on r/csharp, claiming the achieved to implement an audio engine for C# with zero allocations when using it, or in their words, "entirely GC-free" (not a citation, but it's essentially what they said). Luckily, because of their rather easy-to-discover repository structure, it was rather quick to find many occasions where allocations still occurred in their code, and that's despite their claims. They got some pushback from the community in the comments.\ It turns out, just listening to your LLM of choice telling you what you want to hear, in their case that they achieved the almost impossible feat of creating a .NET user library with no allocations and no pressure on the GC, is not a good idea. You believe those claims and spread them further, because you don't understand the code anymore. At this point it's not your code any longer, it's not even your project anymore, it's the AI's project.

Why did I tell you this? Well, because of my inability to quickly understand your code and verify the claims you make about it, I need to ask you a question about the following part of your original post:

Svelto.Tasks is an engine-agnostic, multithreaded and zero allocation task runner for C#, born from the need to have a game centric tasks framework with features that .NET tasks couldn't provide.

What exactly is a "zero allocation task runner"? Is the "task runner" without allocations? Does it run the task without causing any additional allocations? What does "zero allocation" mean in this context?

I'm sorry for doubting you, but past experiences show that AI-project authors often do not really understand what they are talking about and the implications of that.

-3

u/sebasjammer 22h ago edited 22h ago

Don't worry, but if you check my blog you will have less reasons to worry about my work :)

Svelto.tasks is a long living library that I used in robocraft and card life and many other games I worked on... robocraft is a bit old now, but was pretty popular back in the days.

Recently I used uni task for a new project and quickly realized I needed to go back to svelto tasks, which I completed during the development.

I needed however to clean up the repository and add a lot of tests and I couldn't have done it without the AI helping me.

The library is allocations free until, of course, data structures need to resize (like you keep on running new tasks without ending previous ones, as they need to be stored somewhere) , but if you run examples and tests in release you will see that it's true (there are even tests to check the statement)

The library does a ton of things, that's why I added so many examples in the main repository.

It should be simple to pick up if you know already how iterator blocks and coroutines work, but yeah some of the tricks might need a bit of more digging.

Edit: for me allocation free means that allocations won't happen every frame at run-time if the code is used as expected. Allocations can happen at initialization time.

4

u/fruediger 22h ago

The library is allocations free until, of course, data structures need to resize (like you keep on running new tasks without ending previous ones, as they need to be stored somewhere)...

So you're talking about object pooling? But if they need to "resize", they need to (re)allocate, then it's not "allocations free", right? It depends on the user's scenario, if you can keep allocations to a minimum (note that I didn't even say "keep it allocation free" here, because you would still need to allocate the initial capacity of your object pools). Remember that each allocation contributes to GC-pressure, even if they're long-lived object that eventually land in Gen2. And GC-pressure might be the better metric to look out for.

Anyways, what do you do with overallocated memory? Once your data structures resized bigger and the newly allocated additional memory isn't needed anymore some time later, what do you do with it? Do you keep this stale memory around? Don't get me wrong, this is a viable strategy. Or do you reallocate you data structures smaller again? In which case it would be still an allocation.\ I would rather much prefer an external library that I want to use in my projects to release overallocated memory or even give it back to the GC than it nom-nom-noming my main memory.

-4

u/sebasjammer 22h ago edited 22h ago

good observation! I did want to move some of my datastructures from continuous memory to segments based allocations, but this was too much work at the moment.

The allocation runners memory occupation is anyway in the order of few megabytes, because I do not expect people to run hundreds of thousands of tasks at once, there isn't much of a use case for that and iterating so many tasks would be a burden on the CPU anyway, even if the libraries tries to use structs as much as possible, Iterator blocks themselves are still objects.

There are a couple of important pools in the library. One internal to pool continuations, the other for the user to be able to pool Iteratorblocks (which allocate if you need to create them every frame). The rest is not based on pooling, but on arrays storing structures.

5

u/fruediger 22h ago

good observation!

Are you answering me with an AI? Or did you communicate so much with LLMs that you adopted their way of speaking? Or maybe even, are you an AI yourself?

I did want to move some of my datastructures from continuous memory to segments based allocations, but this was too much work at the moment.

This has nothing to do with how you deal with overallocations, it would just determine how (easily) you can reverse them. Although, the latter would at least allow you to reduce reallocations.

The allocation runners memory occupation is anyway in the order of few megabytes, because I do not expect people to run hundreds of thousands of tasks at once, there isn't much of a use case for that and iterating so many tasks would be a burden on the CPU anyway, even if the libraries tries to use structs as much as possible, Iterator blocks themselves are still objects.

A few megabytes are still allocations. Also, even if you don't anticipate your library to be used in that way, you can never be really sure about that. There might be some users out there that, accidentally or intentionally, use your library in unexpected ways. You need to either document these limitations clearly or implement safeguards in your code.

One internal to pool continuations, the other for the user to be able to pool Iteratorblocks (which allocate if you need to create them every frame).

So again, you're allocating.

The rest is not based on pooling, but on arrays storing structures.

I'm not even sure what that's supposed to mean. By "arrays storing structures", do you mean static deterministic memory?

Overall, what I get of this is that you're saying is that your library is indeed allocating and that you can't hold your initial claim of being allocation-free.\ Or did I misunderstand something?

Begs the question of why you made such claims in the first place. Is it because AI told you those things about your project and you blindly trusted it? Or is it because you prompted the AI to do such hard-to-achieve feats and you didn't verify if it actually managed to achieve them?

-4

u/sebasjammer 21h ago edited 21h ago

haha yeah it sounded like AI didn't it :)

only observations are:

you create iteratorblocks, not the library, so the library provides a workaround to avoid even that, giving you the possibility to re-use iterator block state machines even when the task is finished. Consider that even .Net tasks state machines are allocated, but .net doesn't have a way to re-use them.

Array of structures means that the library doesn't allocate objects internally (except for the pooled continuation object), because it uses only structures that wrap objects you pass externally (the iterator block)

The library also assumes you know what you are doing, so you can pre-allocate runners if you do not want to pay the price of run-time allocation, which anyway do not happen every frame.

There is NO complex game that will not allocate every now and then, what you need to achieve is to avoid allocations that happen every frame.

10

u/[deleted] 23h ago edited 23h ago

[removed] — view removed comment

1

u/[deleted] 19h ago

[removed] — view removed comment

0

u/[deleted] 17h ago

[removed] — view removed comment

2

u/[deleted] 17h ago

[removed] — view removed comment

1

u/[deleted] 17h ago

[removed] — view removed comment

1

u/FizixMan 13h ago

Thread removed: Gone a bit too far off the rails.

1

u/zenyl 13h ago

And the AI-generated post too, presumably?

1

u/FizixMan 13h ago

While there are some AI elements in the post text, it doesn't fully read like AI to me or the AI detectors, or like the multitude of zero effort AI written submissions that have been removed.

Obviously the readme/"blog" post have stronger AI elements, especially with how comprehensive it is. The library itself is older and pre-AI, but yes AI was used in the "2.0" update to it. But the code changes do not appear to be a one-shot "Claude, make rocket go now, make no mistakes" lazy zero-effort work.

At worst, it's maybe borderline encroaching on Rule 7, but /r/csharp has generally leaned towards leniency for borderline cases. Especially for users who have a long legitimate history of interactions and contributions on reddit. As opposed to some month-old account using LLM for all comments and spammed their "Claude I made this!" repo with zero history.

1

u/zenyl 13h ago

Even if we disregard the actual repo or "article", the post itself certainly seems to fall into the category AI-generated content. OP stated that this post was written using AI, and it checks several of the most generic AI tells: the unnatural use of bold, the obligatory em-dashes, a bullet-point list, escaped markdown syntax (in the bullet-point list), link emoji prepending links, and as you mention, it promotes a rewrote which (as OP has stated) was largely made using AI.

Seems to check many of the same boxes as so many other now-removed posts do.

And while it may seem petty to point out, the karma score of the post and my comments seem to imply that there's a general sentiment in the community against these types of AI-generated posts.

-6

u/[deleted] 23h ago edited 22h ago

[removed] — view removed comment

6

u/[deleted] 22h ago

[removed] — view removed comment

-1

u/[deleted] 22h ago

[removed] — view removed comment

3

u/[deleted] 22h ago

[removed] — view removed comment

0

u/[deleted] 22h ago

[removed] — view removed comment

4

u/[deleted] 22h ago

[removed] — view removed comment

-1

u/[deleted] 22h ago

[removed] — view removed comment