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

Green Thread Experiment Results #2398

Closed
davidwrighton opened this issue Sep 8, 2023 · 61 comments
Closed

Green Thread Experiment Results #2398

davidwrighton opened this issue Sep 8, 2023 · 61 comments
Labels
area-green-threads Green threads

Comments

@davidwrighton
Copy link
Member

davidwrighton commented Sep 8, 2023

Update

For the runtime-async feature currently in development, see dotnet/runtime#109632


Our goal with the green thread experiment was to understand the basic costs and benefits of introducing green threads to the .NET Runtime environment.

Why green threads

The .NET asynchronous programming model makes it a breeze to write application asynchronous code, which is crucial to achieve scalability of I/O bound scenarios.

I/O bound code spends most of its time waiting, for example waiting for data to be returned from another service over the network. The scalability benefits of asynchronous code come from reducing the cost of requests waiting for I/O by several orders of magnitude . For reference, the baseline cost of a request that is waiting for I/O operation to complete is in the 100 bytes range for async C# code. The cost of the same request is in the 10 kilobytes range with synchronous code since the entire operating system thread is blocked. Async C# code allows a server of the given size to handle several orders of magnitude more requests in parallel.

The downside of async C# code is in that developers must decide which methods need to be async. It is not viable to simply make all methods in the program async. Async methods have lower performance, limitations on the type of operations that they can perform and async methods can only be called from other async methods . It makes the programming model complicated. What color is your function is a great description of this problem.

The key benefit of green threads is that it makes function colors disappear and simplifies the programming model. The green threads should be cheap enough to allow all code to be written as synchronous, without giving up on scalability and performance. Green threads have been proven to be a viable model in other programming environments. We wanted to see if it is viable with C# given the existence of async/await and the need to coexist with that model.

What we have done

As part of this experiment, we prototyped green threads implementation within the .NET runtime exposed by new APIs to schedule/yield green thread based tasks. We also updated sockets and ASP.NET Core to use the new API to validate a basic webapi scenario end-to-end.

The prototype proved that implementing green threads in .NET and ASP.NET Core would be viable.

Async style:

var response = context.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
response.ContentLength = payload.Length;
// Call async method for I/O explicitly
return response.Body.WriteAsync(payload).AsTask();

Green thread style:

var response = context.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
response.ContentLength = payload.Length;
// Call sync method. It does async I/O under the covers! 
response.Body.Write(payload);

The performance of the green threads prototype was competitive with the current async/await.

ASP.NET Plaintext Async Green threads
Requests per second 178,620 162,019

The exact performance found in the prototype was not as fast as with async, but it is considered likely that optimization work can make the gap smaller. The microbenchmark suite created as part of the prototype highlighted the areas with performance issues that future optimizations need to focus on. In particular, the microbenchmarks showed that deep green thread stacks have worse performance compared to deep async await chains.

A clear path towards debugging/diagnostics experience was seen, but not implemented.

Technical details can be found in https://github.com/dotnet/runtimelab/blob/feature/green-threads/docs/design/features/greenthreads.md

Key Challenges

Green threads introduce a completely new async programming model. The interaction between green threads and the existing async model is quite complex for .NET developers. For example, invoking async methods from green thread code requires a sync-over-async code pattern that is a very poor choice if the code is executed on a regular thread.

Interop with native code in green threads model is complex and comparatively slow . With a benchmark of a minimal P/Invoke, the cost of making 100,000,000 P/Invoke calls changed from 300ms to about 1800ms when running on a green thread. This was expected as similar issues impact other languages implementing green threads. We found that there are surprising functional issues in interactions with code which uses thread-local static variables or exposes native thread state.
Interactions with security mitigations such as shadow stacks intended to protect against return-oriented programming would be quite challenging.

It is possible or even likely that we could make the green threads model (a bit) faster than async in important scenarios. The key challenge is that this capability would come with a cost of it being significantly slower in other scenarios and having to give up compatibility and other characteristics.

It is less clear that we could make green threads faster than async if we put significant effort into improving async.

Conclusions and next steps

We have chosen to place the green threads experiment on hold and instead keep improving the existing (async/await) model for developing asynchronous code in .NET. This decision is primarily due to concerns about introducing a new programming model. We can likely provide more value to our users by improving the async model we already have. We will continue to monitor industry trends in this field.

@jkotas jkotas added the area-green-threads Green threads label Sep 9, 2023
@rogeralsing
Copy link

Interop with native code in green threads model is complex and comparatively slow . With a benchmark of a minimal P/Invoke, the cost of making 100,000,000 P/Invoke calls changed from 300ms to about 1800ms when running on a green thread.

It would be super interesting to hear why and why the overhead is so significant.
Do we have some comparisons to other languages with green threads, is it as significant there too?

@bitbonk
Copy link

bitbonk commented Sep 9, 2023

What kind of improvements of the existing (async/await) model do you have in mind?

@yugabe
Copy link

yugabe commented Sep 9, 2023

Sounds great, and I believe this to be the right decision based on the outcomes and results described. Maybe solving the problem some other way would be more efficient, by not making drastic changes to how the current coding model is and turning everything upside down. If there was a way to correctly, safely and performantly "await" async calls in sync code (if you could use await in sync code essentially), not having green threads wouldn't even pose as big a problem. Or even if a method itself wasn't being sync or async based on its signature alone, but rather its usage and analyzed control flow.

@ladeak
Copy link

ladeak commented Sep 9, 2023

I have seen numerous existing, LOB applications (being developed for over a longer period), that do still do many-man sync IO - that nobody can reasonably update to async/await as the whole stack needs to become async/await too. Typically, there are thousands of sync EF/EFCore DB requests implemented. Having a switch to enable green threads to get a significant perf gain sounds very appealing for these scenarios.

@StefanKoell
Copy link

What about other scenarios? I understand that for a web server the conclusion might be spot on but if you are coding UI apps (like WinForms) and heavily rely on consuming event handlers, green threads might be a better solution. Async/await is great for specific use cases but horrible for other use cases. Having an alternative (even when a bit slower) would be very welcome.

@maxkatz6
Copy link

maxkatz6 commented Sep 9, 2023

@StefanKoell can you give an example where green threads are better for GUI apps?

From my experience, event-like nature of async/await works pretty good in GUI apps. And current sync context based infrastructure also works well.

The only possibly disadvantage is sync event handlers. But "async void" there behaves as expected, where any exception is not lost, but raised on the dispatcher (UI thread jobs queue).

@StefanKoell
Copy link

@maxkatz6 maybe I'm missing something but having async void methods (event handlers) will swallow exceptions and are hard to handle in general.

The only possibly disadvantage is sync event handlers. But "async void" there behaves as expected, where any exception is not lost, but raised on the dispatcher (UI thread jobs queue).

Not sure I understand that. Can you elaborate?

Another really pain is to work on brown-field projects and try to implement new functionality using async/await. It's so painful because it's a rabbit hole where you have to rewrite every single funtion in the call chain to make it work. And sometimes, you just can't do it properly because of some 3rd party dependency and you are forced to use GetAwaiter().GetResult - which could cause deadlocks.

The same is true for events which have CancelEventArgs. There's no elegant solution. You have to implement ManualResetEvents, so the code gets really complicated.

If I need functionality which runs sync and async I basically have to write it twice. There's no easy way to just tell a sync method to run async or vice versa (without the hurdles of GetAwaiter().GetResult0.

I was hoping that Green Threads are universal and could simply decide whether to run a method sync or async without all the pain and gotchas mentioned above. That would have been nice and suitable for many scenarios - especially in large brown-field projects.

@SmartmanApps
Copy link

The downside of async C# code is in that developers must decide which methods need to be async.

Affirmative on that one, and so...

Call sync method. It does async I/O under the covers!

...this sounds really good!

I'm currently wanting to implement an interface which will work with either the native (i.e. .NET) filesystem, or use a 3rd party (e.g. Dropbox). Currently for creating a folder .NET ONLY provides a sync method, and Dropbox ONLY provides async methods (sigh). It would be great to not have to worry about that.

Anyone reading this who knows, I'm currently looking for the best pattern to use to implement this. i.e. the method may or may not be async depending on which provider is currently in use.

@Scooletz
Copy link

Scooletz commented Sep 9, 2023

I cannot express how massive this work looks. Being given a lot of effort put into asyncification of projects out there, are you planning to lay out some migration map as well? Or it much to early to have this discussions? There is a lot of projects that spent a lot of time on making their code bases async-friendly when Tasks arrived. Having some good guidance would be helpful.

@Eirenarch
Copy link

What about other scenarios? I understand that for a web server the conclusion might be spot on but if you are coding UI apps (like WinForms) and heavily rely on consuming event handlers, green threads might be a better solution. Async/await is great for specific use cases but horrible for other use cases. Having an alternative (even when a bit slower) would be very welcome.

I'd expect that desktop apps are much more likely to have use cases where they do native interop and therefore green threads are likely more problematic for them than for ASP.NET apps

@StefanKoell
Copy link

@Eirenarch maybe I'm missing something but why exactly would that be a problem? I haven't really seen any sample code how green threads are used exactly and how the programming model looks like. What would be problematic?

@Eirenarch
Copy link

@Eirenarch maybe I'm missing something but why exactly would that be a problem? I haven't really seen any sample code how green threads are used exactly and how the programming model looks like. What would be problematic?

IIRC (don't quote me on that) when using green threads the runtime constantly plays with the (green)thread stack which means that calling native code which is not aware of these swaps will fail. To work around this the runtimes which use green threads do a bunch of marshalling which kills the performance of native code calls.

@riesvriend
Copy link

Great that you did this experiment and mapped a path forward. Too bad it’s on hold however. Because

Line of business apps and domain logic code greatly benefit from having a lower tier concern such as IO hidden from the code. Its now as if c# is moving towards C and away from its VB roots.

Asynch is very verbose in the code and gets most attention, where as the coder/user wants the OOP model to be the focus and tied to business entities, not IO concepts or infrastructure

Eg “total = order.Lines().Sum(line => line.Amount), and not “total = await (order.Lines()).Sum(l => l.Amount).

it’s not for all cases, like a web server module, but for classic VB style readability in business workflow apps green threads would as much a win as hot update support in C# was in continuation of VB6 debug-edit-and-continue. So I hope you follow the edit and continue roots.

@rcollette
Copy link

rcollette commented Sep 9, 2023

I never really understood why async Task<T> couldn't be inferred when using await in a method. ex.

public string DoSomethingAsync(){
    string x = await getSomeValueFromTheDB();
    // Do more work
    return x;
}

Why can't the compiler infer that the method is actually returning async Task<string> in this case? Frequent non-business logic typing would be eliminated in this case.

And for that matter, why can't it infer, when I am assigning a Task return value to a non Task variable, that I want to use await in that case? You would have a compiler error anyway so you're forced to await and in a forced situation, can't the compiler make an inference? For the less common case where you need to do Task.AwaitAll or similar then you would assign return values to Task<T> variables and you wouldn't need to use the async keyword in this case either.

Make the behavior a compiler switch for those that prefer their code to be explicit.

@CyrusNajmabadi
Copy link
Member

I never really understood why async Task couldn't be inferred

It could be. The question is: is that good thing?
For example, if you do that silently, give now made a trivial binary breaking change without the user ever being aware of it.

For that reason, we prefer the abi to be explicit. We could have gone a different direction, but we believe it's better this way.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Sep 9, 2023

Why can't the compiler infer that the method is actually returning async Task in this case?

Because that might be very wrong and undesirable. Many other task-like types might be more appropriate there.

It's important to understand that Task/Task<> are not special from the perspective of the language. They're just one of many possibilities in an open ended set. We don't want the language making such opinionated choices unless there really is only one true choice that is going to be right all the time. Especially not for something as important and flexible as asynchrony

@CyrusNajmabadi
Copy link
Member

And for that matter, why can't it infer, when I am assigning a Task return value to a non Task variable, that I want to use await in that case?

For the same reason as above. This would actually be hugely bad for at least one large group of customers depending on how we did this. Either we would do the equivalent of a naked-await, and be bad for libraries. Or we'd do the equivalent of ConfigureAwait(false) and be terrible for things that depend on sync contexts.

Different domains need different patterns, which is why this is an intentionally explicit system.

@CyrusNajmabadi
Copy link
Member

Make the behavior a compiler switch for those that prefer their code to be explicit.

We are strongly opposed to dialects of the language on principle. We've only added one in the last 25 years, and only because the benefit was so overwhelming, and the costs too high for the ecosystem otherwise for the success of that feature.

That doesn't apply here, so it would be highly unlikely for us to go that route and bifurcate the ecosystem.

@rcollette
Copy link

From my perspective, things like minimal APIs and top level statements bifurcate the system, but it seems like there was a justification for doing so, ease of adoption I believe being one of them, and ease of use is something myself and others find relevant.

There are people/systems that have different goals. Writing a highly engineered performant library that is going to be used for video processing, or at a scale like Netflix, Amazon and others, those warrant utilization of very explicit Task types (and Spans, Vectors, etc.), and it's fantastic that .NET provides such great performance in these scenarios. For a large number of people, there is a reduced level of performance scrutiny required, as they balance out their workload with the need to produce more business logic. "Wrong/Right" is a matter of perspective and situation.

Like others, I've found myself in a situation where something that I never would have thought would be async (I don't know, setting a message header or JSON serialization) suddenly became an async operation and next thing you know you're in a rabbit hole of updates. Having the compiler handle that, when its able to, is a win, at least for some of us, even if it's not perfect from an engineering perspective, because it gets you to a better place and you can always go back and be more explicit if need be. Premature optimization can be a fault.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Sep 9, 2023

For a large number of people, there is a reduced level of performance scrutiny required,

If performance isn't a concern then just keep everything synchronous. For any async-only apis, just FYI sync-over-async. It's not good, but it's not going to matter if you're on a reduced perf scrutiny environment. :-)

Like others, I've found myself in a situation where something that I never would have thought would be async (I don't know, setting a message header or JSON serialization)

Premature optimization can be a fault.

In this case, you didn't have to go full async. Esp if you are not in a high perf scenario (like you mentioned). Just do the simple thing here and you'll get a solution that works with minimal fuss or perf impact. Likely far less impact than if you had to switch to Green threads. :-)

@CyrusNajmabadi
Copy link
Member

Wrong/Right" is a matter of perspective and situation.

Sure. I'm just explaining the perspective as one of the language designers. That there are many of these groups, and we don't want to bifurcate over them, led to the decisions I was commenting on.

@CyrusNajmabadi
Copy link
Member

and top level statements bifurcate the system,

They don't bifurcate the language (which is what I was responding to when I was discussing compiler switches). Top level statements are just c#. They're not an alternative set of semantics that you need to opt into with a compiler switch that then changes them meaning of existing code.

Having different meanings for the same code is what we mean by 'dialects' and 'bifurcation'. Having the language support more, while preserving samantic-compat with the last 25 years of c# is fine :-)

@riesvriend
Copy link

Make the behavior a compiler switch for those that prefer their code to be explicit.

We are strongly opposed to dialects of the language on principle. We've only added one in the last 25 years, and only because the benefit was so overwhelming, and the costs too high for the ecosystem otherwise for the success of that feature.

How about a new block type to auto-await code? It essentially a matter of preprocessing for the purpose of domain clarity over IO/concurrency clarity.

await { var total = order.Lines().Sum() }

@CyrusNajmabadi
Copy link
Member

How about a new block type to auto-await code?

Sure. Feel free to propose and design over at dotnet/csharplang. If the proposal picks up steam, it could definitely happen.

@mostmand
Copy link

What I realize from the discussion is that the existing async/await pattern that has been the recommended approach for i/o intensive workloads has become too much of a burden to coexist with green threads. I wish that some brand new language would come to dotnet runtime with no backward compatibility concerns.

@kant2002
Copy link
Contributor

I would like to express "dissatisfaction" with report. I honestly expect it to be more technical in nature, and not "informational" only. I understand that a lot of time passed after experiment was done, and maybe some details is faded in memory. But if possible that it was cut due to lack of time for preparing more technical explanation, I really would like to read more about what was broken/complicated. And probably what was surprisingly easy to do.

@tannergooding
Copy link
Member

I wish that some brand new language would come to dotnet runtime with no backward compatibility concerns.

.NET is itself a 20yo ecosystem, it is not just the languages, but also the runtime, the libraries, the tooling, etc. You will always have back-compat concerns and the only way to get rid of some of those concerns would be to start a new ecosystem from the ground up. That being said, starting a new ecosystem would be a massive undertaking for honestly little benefit. Sure there's things we wish were different, but none so much that they cause unmanageable or unreasonable to handle issues; and none that are so fundamental that it necessitates starting over.

I'd also point out that the developers that would most benefit from green threads are the ones that can't or won't rewrite their app to use proper async/await. Such codebases would be unlikely to get approval to move to a new ecosystem/language for similar reasons as to why the async/await rewrite is rejected. Often this is that the short term cost is "too high", even if the long term benefit justifies it.

Finally, green threads are not some magic feature that simply make everything better. They are a feature that can make some types of existing code better and which come with their own negatives/drawbacks. Namely it can improve existing purely synchronous code by making it perform closer to how properly written async/await code would, but it gives the devs less control and may not work well with scenarios that leave the "green thread aware" domain (the primary example of which is interop with native or other languages like Java/ObjC) or with scenarios that are trying to do more explicit control via explicit tasks or even explicit threading. So while it might help code that can't migrate; it could inversely hurt code that has already migrated.

-- Views/thoughts here are my own and may not be shared by everyone

@Xhanti
Copy link

Xhanti commented Sep 10, 2023

@tannergooding your points are well reasoned, but it doesn't address the can't make it async scenario. In a scenario where you can't make things async all the way(dependency you are not in control of) and you have to 'hack' it (sync over async) there is no good answer. That's always bothered me and green threads seemed an elegant solution. The issue around native interop and interoperability with other languages is a significant issue but I don't know.

It's trade offs, maintain backwards compatibility, performance of interoperability, finer control over async code vs drastically simpler developer mental model of async code.

The older I get and the more code I write the more I lean towards simpler mental models. I care so much about it I can leave some perf on the table to keep things simpler. But that's just my limited experience.

Having said that, it's your guys call which way the trade off goes and the team has presented a rational decision. I don't have to like it, but I understand and respect it.

@agocke
Copy link
Member

agocke commented Sep 25, 2023

For anyone looking for more technical details, a report has been checked in with what we found: https://github.com/dotnet/runtimelab/blob/bec51070f1071d83f686be347d160ea864828ef8/docs/design/features/greenthreads.md

@shybovycha
Copy link

shybovycha commented Sep 26, 2023

Correct me if I'm wrong, but iirc, currently .NET generates a state machine for every await call.

I am not sure why the decision was made to develop a whole new approach, but was the option to replace the generated state machine with green threads even considered? This would not break existing APIs or language paradigms and would improve the existing applications' performance, as I see it.

@HaloFour
Copy link

@shybovycha

Correct me if I'm wrong, but iirc, currently .NET generates a state machine for every await call.

The C# compiler emits a state machine for every async method, but not one for every await call. Not all methods that return Task<T> or a task-like are an async method or have generated state machines, though.

I am not sure why the decision was made to develop a whole new approach, but was the option to replace the generated state machine with green threads even considered? This would not break existing APIs or language paradigms and would improve the existing applications' performance, as I see it.

From the technical details posted it sounds like the teams did explore this avenue, by having existing blocking I/O methods detect whether they were on a green thread and wiring up notification before yielding. They did rely on Task<T> and existing async methods in the BCL to a point but that was probably to having to refactor a ton of code. Green threads incur their own overhead, and also require some kind of state machine allocated on heap in order to respond to the I/O callback and wake the thread. I'd expect that even if the team didn't use Task<T> and some existing machinery that it would still have been less performant, or a wash at best. Only way I think the team could prove that would be something more synthetic and rewriting as low of an I/O call as they could using completely different machinery.

@qouteall
Copy link

@rogeralsing If I understand correctly, the main cost of increased foreign function call overhead comes from stack switching. Green thread is initialized with a small stack and grow by-demand, to reduce memory overhead of having many green threads. The called native code does not have stack growing functionality, so not switching stack could cause stack overflow.

Golang does stack switching when calling FFI which is also slow.

@obratim
Copy link

obratim commented Oct 16, 2023

I would like to remind that async/awit serve not just for performance, but more importantly to describe the behaviour of the program, so that programmer himself could later understand what program is doing

I, personally, dont want the runtime to implicitly create and run some "green threads"

I am afraid that the productivity of a develper would only decline with sutch feature because programs may become more bug prone

@Jeevananthan-23
Copy link

Hi, I'm junior system dev who want C# be a better System Programming Language. C# current Async state machine model is really good to handle I/O bound which running in user space(not kernel). Where the recent evolation of system langs like Rust/Zig much care about high performance, control and most imp Memery safety. More over io_uring in Linux space and IOCP in Wids space way to go for thus langs to async work more fater. And I'm hearing some news about Microsoft integrating and investing more in Rustlang which is good alternative for C++ where the CORECLR is writen in am I wrong here ?

After the Green Thread result it's clear that async programming is more faster better to work on async model to beat Golang.

Pointing out zig issue: ziglang/zig#8224

@sgf
Copy link

sgf commented Jan 8, 2024

I think the caller should decide whether to be asynchronous or not, which means that the API should all be synchronous. When the caller wishes to make asynchronous calls, the compiler wraps them as asynchronous.
The current asyn/await+Task combination is obviously a very intrusive design. This is very unfriendly and even breaks up the ecology.

So as far as I can see either .net style :
response.Body.WriteAsync(payload).AsTask();
Or the style of green thread:
response.Body.Write(payload);
Neither is good enough.
.net styles require the coordination of underlying libraries.
And the same goes for Green Thread. From a grammatical level only: The advantage of green threads is that the underlying asynchronous logic can be called externally in the same way. But the disadvantage is: the outside cannot see whether it is an asynchronous call.
The advantage of .net is that it can be seen from the outside that it is an asynchronous call, but the underlying library needs to be modified on a large scale.

@HaloFour
Copy link

HaloFour commented Jan 8, 2024

@sgf

I'm not sure what you're suggesting there. Without callbacks (facilitated by async/await coroutines) or green threads, you're back to blocking kernel threads. There's nothing that the caller can do there, short of spinning up a separate thread to run that code, which is exactly what we're trying to avoid given it's expensive and wasteful.

The closest you get to allowing the caller to determine whether it's async or not is via green threads, by virtue of the caller having to run within a green thread in order for the asynchronous method to be able to park the thread at all. But you still run into the problem that you need the entire ecosystem under that method to be written in a manner that supports notifications and unparking the green threads. That requires splitting the ecosystem, whether that be through relying on existing asynchronous APIs, or having the methods manage in internally. Otherwise, you're back to blocking kernel threads, even if they happen to be executing a green thread.

Either way, everything under the hood has to be written to be async-friendly. It has to call specific APIs that support notifications and handle all of the plumbing to resume operation. Both approaches are necessarily viral (tasks or green threads all the way down) otherwise you still block kernel threads.

@NCLnclNCL
Copy link

NCLnclNCL commented Jan 10, 2024

When will have it, i dont want use async, await method and normal method, which need 2 method

@sgf
Copy link

sgf commented Jan 13, 2024

@sgf

I'm not sure what you're suggesting there. Without callbacks (facilitated by async/await coroutines) or green threads, you're back to blocking kernel threads. There's nothing that the caller can do there, short of spinning up a separate thread to run that code, which is exactly what we're trying to avoid given it's expensive and wasteful.

The closest you get to allowing the caller to determine whether it's async or not is via green threads, by virtue of the caller having to run within a green thread in order for the asynchronous method to be able to park the thread at all. But you still run into the problem that you need the entire ecosystem under that method to be written in a manner that supports notifications and unparking the green threads. That requires splitting the ecosystem, whether that be through relying on existing asynchronous APIs, or having the methods manage in internally. Otherwise, you're back to blocking kernel threads, even if they happen to be executing a green thread.

Either way, everything under the hood has to be written to be async-friendly. It has to call specific APIs that support notifications and handle all of the plumbing to resume operation. Both approaches are necessarily viral (tasks or green threads all the way down) otherwise you still block kernel threads.

I think the go language handles this aspect very well. It has no magic at the usage level and looks simple. The difference between synchronization and asynchronous is just a go keyword.
The design of the Go language embodies the design of dependency inversion in programming languages. Whether asynchronous is supported depends on whether the caller needs it, not whether the callee provides it.

And the goroutine+chan+select method can be applied to most situations.

If possible, may be able to use the @ symbol to represent asynchronous calls (currently the @ symbol should only have a keyword escaping function when calling a function prefix? But we might as well add this function), or maybe can directly implement the go keyword of the go language.

@CallMathod();//Use the @ symbol to tell the compiler that a synchronous method needs to be called asynchronously
go CallMathod();//Use the go keyword to tell the compiler that the synchronous method needs to be called asynchronously.

We already have BlockingCollection and Channel
Then we will implement a tool related to the Select mode, and it seems that we can perform programming similar to Go.

Of course, my understanding of the Go language is still a few years ago.
But one thing I know is that the Go language is currently being widely used in IO-intensive demand areas such as databases, docker, and network applications.C# in these areas,Although it cannot be said that it has no achievements, it is still very rare.

@sgf
Copy link

sgf commented Jan 13, 2024

When we do something asynchronous, we don't necessarily need a callback. Often we just need a result, which is a concurrent structure.
For example, BlockingCollection is used to receive asynchronously returned results.

rlt is an AsyncResult<BlockingCollection<T>> or AsyncResult<Channel>

var rlt= go CallMethod;//Asynchronous execution
var rlt=@CallMethod;//Asynchronous execution

CallOtherMethod();  //executes other synchronously

if(rlt.OK/Error/Other) is similar to await, here you can wait for the result synchronously.

@HaloFour
Copy link

@sgf

I think the go language handles this aspect very well. It has no magic at the usage level and looks simple. The difference between synchronization and asynchronous is just a go keyword.

The difference with Go is that everything is a green thread. The process starts on a green thread, and go launches more of them. It's always non-blocking because you're not given any choice in the matter. At best you're given the illusion, but given results would be propagated back via channels or callbacks that distinction doesn't even really matter.

But one thing I know is that the Go language is currently being widely used in IO-intensive demand areas such as databases, docker, and network applications.C# in these areas,Although it cannot be said that it has no achievements, it is still very rare.

I suspect that ASP.NET is used much more widely than Go for server-side applications.

@vukovinski
Copy link

vukovinski commented Jan 13, 2024

My 2 Euros:

I think we should start thinking about CPU compute threads and PCI signals as the un-parking mechanism (flags).

Especially curious to see how one motherboard would differ to another one performance-wise.

Edit: then we could use the GPU as a memory bank when not handling heavy graphics.

Proof: Ideally cubical computer. Asymptotic n-linear space. Threads are being spawned out through (managed?) IRQs. Regular logic. QED.

Controversy: It's not a geometric logic, yet.

Codename: #KernelNT2

@DiXaS
Copy link

DiXaS commented Oct 31, 2024

I am disappointed that green threads were abandoned. I think the comfort of writing code is much more important than losing small performance (or giving people a choice). Java is looking interesting again with new technology.

@sgf

This comment has been minimized.

@CyrusNajmabadi
Copy link
Member

@sgf that message is not acceptable. Everyone is free to state their thoughts and opinions here. We don't gatekeep here.

@sgf

This comment has been minimized.

@sgf

This comment has been minimized.

@terrajobst
Copy link

@sgf

We warned you last year that this kind of conduct isn't acceptable. Given your continuous attacks we're left with no option but blocking you.

@ris-work
Copy link

I would like to see a way to do things without having to resort to special syntactic choices like async/await, where everything is async similar to Elixir/Erlang. I do understand that it is very hard and there is no objectively right answers; C# is the main language which introduced async first.

I have written async/await code in Rust too, and it too is fragmented with 'special' syntax with things being a pain to mix and had rampant performance problems when I had to limit the throughput with async Mutexes or Semaphores so I wrote everything to use just OS threads. At least in C# we have Task<...>.GetAwaiter().GetResult() if you have exactly one thing to do and that is to wait, and it also plays nice with [STAThread]s and other single-threaded UI libraries.

@Luiz-Monad
Copy link

What we really need is for a way for the caller to decide what the implementation of Task should be. Async/Await should work much more like the FSharp does it.
The CSharp compiler must be doing a lot of work in the Async/AWait machinery, work that should have been done by the runtime instead.
I still think green threads would help avoid the CSharp compiler having to do a lot of work there.
Maybe the entirety of all Tasks in an entire process could be replaced with a GreenThread implementation that would basically just use special new IL instructions to signal the async wait instead of having to create objects, that's what makes async slow, having to create the frames for the stack of the green thread using the heap.
I think the ideal solution would involve using green threads to implement a complete replacement for the TPL library, turn it into the runtime instead of a library that tries the best to do minimal allocations and rely heavily on mutex and locks for that.
Don't change anything from the user point of view, just replace the Task.

@Mr0N
Copy link

Mr0N commented Jan 21, 2025

You could keep the "async" and "await" concept intact but simply change what happens after their compilation. For example, currently, a lot of IL code is generated and executed. Instead of that IL code, everything could be run in green threads.
Alternatively, if these are fundamental changes, you could add some keyword to the signature of async methods to indicate that it's an asynchronous method using green threads.
In theory, green threads should work much faster with "async" and "await" and would not put a load on the stacks.

@HaloFour
Copy link

@Mr0N

See: https://github.com/dotnet/csharplang/blob/1289257476a2aa95a02b879be522a44db561d26e/meetings/2024/LDM-2024-04-01.md

@agocke
Copy link
Member

agocke commented Jan 22, 2025

This issue is still useful as a summary of the experiment, but current efforts are now focused on Runtime-Async, so I'm going to close this issue and people can look at dotnet/runtime#109632 instead.

@agocke agocke closed this as completed Jan 22, 2025
@dotnet dotnet locked as resolved and limited conversation to collaborators Jan 22, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-green-threads Green threads
Projects
None yet
Development

No branches or pull requests