Comments (129)
I'd love to see a [assembly:TaskConfigureAwait(false)]
added to the framework. People are working around this (but not in netstandard
) with Fody today: https://www.nuget.org/packages/ConfigureAwait.Fody
async
/await
is great, but there are a few very big and very consistent pain points for library authors and app owners alike. At the top of my list are:
.ConfigureAwait(false)
almost everywhere- Stack traces being incredibly noisy
We've had async
for over 4 years now and neither of these has improved at all. Any language feature which requires repeating something verbose in almost every place is a bad experience. On top of that, forgetting to add the verbose thing (and many new to async
do) defaulting to failure (overhead in this case) adds to the priority, IMO. Please, let's give library authors a break here.
...or we can make .ConfigureAwait(false)
a VS 2017 built-in keyboard shortcut.
from csharplang.
+1 It's indeed a pain to have ConfigureAwait(false)
everywhere in practice for all libraries.
from csharplang.
Just want to confirm that this is a major pain for library authors. Installing analyzers into hundreds of projects and then having to set them all to Error severity just to ensure we add this ugly ConfigureAwait call is pretty de-motivating.
But another alternative could be to introduce another await-style keyword as syntactic sugar for await with ConfigureAwait ( false ). Not sure what a good name would be though.
from csharplang.
I too find the current default maddening. What percentage does sync-requiring GUI code represent of all C# code being written today, anyway? Sigh.
from csharplang.
How do you guys think of an approach with Task-like (Generalized Async Return Types):
https://github.com/ufcpp/ContextFreeTask
This requires no IL-weaving or no new compiler feature.
from csharplang.
@wachulski This is very practical, but I feel we should have more ambition and aim for cleaner code.
It's like if instead of await
we got an analyzer that lets us write ContinueWith()
better.
from csharplang.
That's actually possible. We could add ConfigureAwait(false); to the completion list off the task.
That'd still produce hude code redundancy.
Personally, I never use the same any line of code more than 2 times per solution. Code repeatedness makes my eyes bleed.
Looking forward to ThreadPool.MakeDefaultConfigureAwait(false);
with a desperate hope in my eyes.
from csharplang.
Almost 3 years since the proposal,
Perhaps it's time to take care of this?
Assembly/Class/Method attributes could save tons of hassle.
There's a Fody addin for that https://github.com/Fody/ConfigureAwait, which we haven't tried, as we prefer a native solution, as modifying the IL doesn't seem like a reliable solution for us.
from csharplang.
Take this sample code:
void Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
Console.WriteLine(SynchronizationContext.Current?.GetType().Name);
M().Wait();
Console.WriteLine(SynchronizationContext.Current?.GetType().Name);
}
async Task M()
{
Console.WriteLine(SynchronizationContext.Current?.GetType().Name);
await Task.CompletedTask.ConfigureAwait(false);
Console.WriteLine(SynchronizationContext.Current?.GetType().Name);
await Task.Delay(10).ConfigureAwait(false);
Console.WriteLine(SynchronizationContext.Current?.GetType().Name);
}
You'll find that the output is this:
WindowsFormsSynchronizationContext
WindowsFormsSynchronizationContext
WindowsFormsSynchronizationContext
null
WindowsFormsSynchronizationContext
ConfigureAwait(false)
has no effect on completed tasks.
If a task is already completed, then the execution continues on the same thread (that might be a thread with a synchronization context). If you don't use ConfigureAwait(false)
on subsequent calls, their continuation will be posted to the captured synchronization context.
So, to be effective, if you use ConfigureAwait(false)
in one method, you'll have to use it on all await
s.
from csharplang.
Agree with @tmat: IL rewriters come with significant downsides:
-
big impact on build performance (ex. In one of our projects we are using fody.propertychanged. It takes 15 seconds on each build to IL rewrite our assemblies).
-
Metadata issues: Problems with debugging, expression evaluators, and non functional ENC.
from csharplang.
@alrz I don't see why not, but I can't say I understand all the edge cases perfectly.
Let me know if I'm missing something here.
from csharplang.
@ashmind What about:
Option C
Incorporate ConfigureAwaitChecker.Analyzer
(counterpart) into native Roslyn analysers set with 2 diagnostics (and fixers):
- Report when
.ConfigureAwait(...)
not explicitly set- opt-out
- warning level by default
- Report when
.ConfigureAwait(false)
not set- opt-in
- error level by default
As for per solution/project configuration means new .editorconfig options might be introduced:
- configureawait_must_be_explicit // false by default
- configureawait_must_be_false // false by default
Those would allow for opt-in/out up to project level basis.
from csharplang.
Check out the other issues that discuss trying to eliminate ConfigureAwait()
calls - it's been extensively discussed.
Essentially, however, my understanding boils down to this:
The language has no knowledge of implementation details. The async and await keywords only require that the types involved are awaitable.
The most commonly used implementation (by far) is the Task Parallel Library (TPL), It's the TPL that introduces the Task
and ValueTask
types, along with the concept of a synchronization context.
Crucially, nothing in the language requires you to use the TPL implementation.
Adding a language feature to address this library issue seems (to me) like the wrong approach. It would irrevocably bind the feature to a very specific implementation, and very likely tie things up so tightly that any future innovation in the area would be impossible.
It would be far better to work out how to move the design of the TPL forward in a useful way that eliminates the ConfigureAwait()
warts while still solving the problems that Synchronization Contexts were introduced to solve.
But, that's a discussion for a different repo.
from csharplang.
I like the original proposal with a few modifications for the consistency:
[ConfigureAwaitDefault(bool? configureAwaitDefault = null)]
should be applicable on assembly, class, and method level (the nearest one wins)<ConfigureAwaitDefault>configureAwaitDefault</ConfigureAwaitDefault>
property should work on .csproj level (this maps to addition of the same attribute on assembly level)configureAwaitDefault
can benull
or omitted - this matches the current behavior.
The option should impact only the code generated by C# async state machine builder. When configureAwaitDefault != null
the builder should additionally invoke ConfigureAwait(configureAwaitDefault)
on whatever is awaited if ConfigureAwait
method is available (or an extension method with the same name). This means you are free to call ConfigureAwait
manually as well, and in this case configureAwaitDefault
won't make any difference.
Thoughts?
from csharplang.
Over the past year, vs-threading has reversed my view on this issue. I am very much looking forward to no longer needing to specify ConfigureAwait
outside of rare edge cases where ConfigureAwait(false)
is required for correctness.
from csharplang.
On a personal level, i like @HaloFour 's proposal the most. The solution is visible in your source and easy to add. I don't like any sort of implicit changes here. Extension operators and implicit assembly changes are highly undesirable ot me. When it comes to await behavior and async/concurrency, i want to be able to see what's going on in the code front and center.
once you have a handful of awaits, i think it's very reasonable to just do:
public async Task<Something> DoSomeStuff()
{
using var _ = Task.DisableSynchronizationContext()
// no more sync context to worry about
var x = await DoSomething();
var y = await DoSomethingElse();
var z = await DoOtherStuff();
return await MixItAllTogether(x, y, z);
}
A very big positive is that it's simple, can be done today, and is very visible in the code. It's something i woudl accept into codebases i work on, unlike several other proposals that are highly problematic.
from csharplang.
the number of libraries should certainly exceed the number of applications that depend on the synchronization context...
Have you heard of Dark Mater Developers?
Scott Hanselman posits (quite reasonably) that the developers you see bloggings, tweeting, posting on social media, attending conferences (where available) and so on are the slim minority of developers. The rest of them are working developers, delivering business value as a part of their 9-5 job, getting their job done and then heading home to enjoy other pursuits.
I'd suggest that the number of libraries requiring asynchrony would be vanishingly small compared with the number of WinForms and WPF applications being written and maintained right now in 2021.
I worked full time as a .NET developer for a bank between early 2004 and late 2015 and I wrote literally dozens of them that are still in daily use.
from csharplang.
Thanks for the replies! I appreciate you taking the time.
I've seen (relatively few) problems going unsolved for years and it seems to happen when a problem generates multiple issues within multiple repositories. That makes it very hard to see who has the ball and what the overall status is.
I don't know if Microsoft has any cross-team meetings to look at "over X-year-old" issues, but I think some coordination could be useful here. To me, it sounds like a problem that Microsoft is able to solve, the right people just need to be put together and focus on this.
If there really is no good solution, not even one that can be defined by a cross-team effort, then that's fine, but then close the issues with proposals you can't/won't implement. Then there is at least some "progress" and a "result".
Even better, elevate the problem to a "Super Challenge" and make a community-wide contest out of it or something, just to let it be known that there is a hard problem that will go unsolved unless something extraordinary is done about it (just a suggestion from a frustrated developer).
from csharplang.
For every JSON.NET library there are millions of apps that consume it. Even for corporate helper libraries, there's no point to them if they're not used by more than one application. I don't have the numbers which led to this decision, but the .NET and C# teams did at the time based on feedback and VS telemetry. I'd easily put money on there being more WinForm internal business apps written every year than all libraries. And I'd wager still that the majority of libraries written are still context sensitive and intended to be consumed by a specific flavor of application.
And no matter how we answer it today, the problem still remains for a nonzero number of people, and it needs to be somehow solved
It is solved, by virtue of being able to call CA(false)
. Yes, this is intentionally more painful for the minority of developers, and it sucks to be in that minority of developers.
I'd also note that, as mentioned above, this isn't really a language issue. The C# language doesn't know anything about context, it only understands the awaiter pattern. What that awaiter does with context is entirely opaque to the language.
from csharplang.
What feedback s and telemetry can be on functionality that did not exist at that time?!
Feedback on the ratio of project types.
And why it was decided to do exactly
ConfigureAwait(false)
but not with something
await? / await! / ^await etc
Because there was no need to make the language aware of it. Adding policy concerns only complicates the language and makes it harder to evolve. ConfigureAwait
is just a normal method that exists on Task
and a handful of other types that provide built-in support for synchronization. Other awaitables could offer entirely different methods to configure synchronization or other policy.
from csharplang.
Proposals for BCL additions can be made on the CoreFX repository as any such helper method won't require language changes. You'd have to describe exactly what you expect it to do.
from csharplang.
@heku The Language Team’s stance on introduction of language dialects is unlikely to change, especially for a flag like this that could make two versions of what is otherwise an identical assembly incompatible.
from csharplang.
It's just mind-blowing and very sad that this is still an issue in 2020.
Microsoft, I don't want to be a jerk, but I truly believe in being honest and direct and I have to say that in my opinion you have drop the ball on this one and it's just not good enough.
You want feedback, you want to be open source, you want a community, well then don't ignore a major plain like this one for years. I get that it's hard to solve and maybe it's falling between two chairs and so on, but it has been years now!
Please, solve this. If I still have to ConfigureAwait(false) things when .NET 5 comes out, you'll have to explain to my kids why my brains are on the ceiling. No, seriously though, I'm happy with my choice to be a .NET developer and the general direction of .NET Core and .NET 5, but you need to fix this and you need to fix it now...
And no, ConfigureAwait.Fody is not an option, it doesn't calculate a new checksum and thereby making it impossible to upload the snupkg to NuGet.
Hoping for a status update and an ETA.
from csharplang.
@Kir_Antipov At the time (almost 10 years ago) the data I had access to had a range. Application developers represented 80%-90% of the .NET developer community with 10%-20% being made up of library developers. Based on that, and the fact that all applications you could build at the time would need ConfigureAwait(true)
it was set as the default. But even without real data lets just go through the intellectual exercise:
Suppose that everyone whoever writes an application (Which we will define as something they are responsible for the deployment of) also writes a library and publishes it online. This would be an amazing universe where:
- Almost all code is novel (If everyone is publishing a library it means that one they could have re-used didn't already exist)
- 50% of all code is used in application and 50% is used in libraries
If we instead say that every developer is not going to write a json library since one already exists then they means that the number of applications over libraries will be >50%.
from csharplang.
For every JSON.NET library there are millions of apps that consume it
Not every library is consumed by millions of users. Moreover, not every library is consumed by more than one application. So, it's still Many-to-Many
and which Many
is greater is still not obvious. And I don't know why are we still arguing about it, because:
-
I won't believe anything without scientific fact being provided. "These guys have private telemetry which says so" is not a scientific fact. Among which participants was telemetry going? In what time period did this happen? How was the final value calculated? How much has changed since then?
-
My belief (based on the personal experience) does nothing with the problem of this topic
It is solved, by virtue of being able to call
CA(false)
No-no-no, stop it right there, sir. It's just like saying: "The asynchronous programming problem was solved by the Thread
class" (A warning for the guy who is about to write that async
!= multithreading
: this was exactly the point, thanks)
This was a completely different problem, the solution of which led to another, smaller (?) problem, and that's why we're all here
I'd also note that, as mentioned above, this isn't really a language issue. The C# language doesn't know anything about context, it only understands the awaiter pattern. What that awaiter does with context is entirely opaque to the language.
I completely agree. The problem is presented by the framework, so it would be nice if framework itself could fix this, or some new general purpose language feature may be introduced that can also help in solving this problem (that's my point, I don't like tricky compiler-behavior switches too)
At the time (almost 10 years ago) the data I had access to had a range. Application developers represented 80%-90%
I got it numerous times: there's some closed telemetry data from like 10 years ago that says so, nice, there's no need to repeat it that many times, it makes nothing.
Well, ok. Let's imagine that the telemetry gave the most accurate results and they're still applicable. Is 10-20% of .NET developers a small number? If they are a minority, do they have to suffer? If they are a minority, do they have no right for a better life? Is minority negligible? I don't think this is the politics of the modern world.
I can understand logic behind CA(true)
as default, but I can't understand why there's still no built-in solution to make library developers' life easier.
Shouldn't we be more nice to library developers? The bare framework is of course still good on its own, but where would we be without all these fancy libraries floating around? Why, for example, is Python so popular? The beauty of the syntax, the speed of work (ok-ok, I'm done, for real now) and, of course, a huge number of the most diverse libraries. What's happening at this time in the world of .NET library developers?
I know a lot of people who eventually gave up on .ConfigureAwait(false)
, they simply decided that if your application has a synchronization context, that's your problem, not theirs. Not the best solution, don't you think? Do I blame them? Can I blame them? Of course not. CA(false)
is terrible: you constantly need to keep it in mind, add it to almost each method call, and periodically wipe the blood pouring from your eyes because of the boilerplate code that is smeared all over your screen.
I even know a good developer who rage quit C#-programming because of this. The poor fellow literally had a mental breakdown after yet another .ConfigureAwait(false)
.
So, it seems like a lot of us have some "CA(false)
-durability" :)
But even without real data lets just go through the intellectual exercise
What's so intellectual about this "exercise"? Finding out the way it's broken? Kinda easy for real "intellectual exercise".
Let's take a closer look at your "amazing universe" and write down the key rules:
- All libraries and apps are unique (
Almost all code is novel
) - Every app depends on 1 library, which describes its business logic (I believe, 'cause it was not stated directly, but depending on some kind of random library in this situation seems way more ridiculous) (
Suppose that everyone whoever writes an application [...] also writes a library
)
If we instead say that every developer is not going to write a json library since one already exists
And here you apply an expression from the real world (so your interlocutors can lose the thread of reasoning while they are busy processing the joy of recognition: "Oh, hey, I use JSON libraries in every project too, seems alright") to your invented world with made up rules. Since this partially breaks the first rule (apps are no longer unique as business logic was fully duplicated, but libraries are) while you're focusing on the second, I'm sure this was done on purpose.
But even that's not all! You used the word "every" very well. So now you're basically comparing the length of an infinite series of constants (apps which business logic's described by json library) to the length of a series of one element (json library itself). And here even an idiot would agree that the number of applications is slightly greater than the number of libraries, which, in fact, is what you wanted to prove.
And this whole thing is called sophistry. Don't get me wrong, big fan, and you did it well, but it's not the right place to do such things, I believe
To end this post, I want to describe my position, because of which I was so sure that the number of libraries outnumbered the number of applications.
To put it simply and briefly, this belief is based on personal experience: every project (personal or corporate) that I have worked on over the past 5 years has "spawned" from one to several dozen new libraries. If you'll take the average ratio of libraries per application from my experience, you'll get something like 4.
In longer terms, you forgot one thing about libraries: they're the logical unit just as functions or classes. If a function becomes too large, we split it into several ones. Small functions are easier to maintain, easier to debug, easier to test (we don't have the complexity index for no reason). The same applies to libraries. Their goal is far from just reusing some code.
Even for corporate helper libraries, there's no point to them if they're not used by more than one application
So the existence of a library that is used by only one project is more than justified.
I hope we're done. If there're still people who want to prove to me that the number of libraries exceeds the number of applications, please re-read the first statement of the first paragraph of this message and do not clog the thread (unless you have actual numbers on hand). We've already gone offtopic too much
from csharplang.
but I can't understand why there's still no built-in solution to make library developers' life easier.
Personally, as a library writer, I find the situation totally fine. I write code without ever writing CA (except the occasional CA(true)). Then, at the end, I use an analyzer/fixer to add an the CA(false)s to everything I didn't annotate.
It's a single step and then I'm done. So, as one of the language designers, I just don't feel any sort of massive need to do something here. This is compounded by having not seen any good proposals that I like here that would even address the issue.
from csharplang.
I'd say that this is a CoreFX issue. ConfigureAwait
is nothing more than an instance method on Task
/Task<T>
, the language provides no specific support for calling it.
from csharplang.
It should be for method, class and assembly level ;)
from csharplang.
@sharwell Can you enlighten us on how vs-threading resolves this (it seems to be focused on applications, and i am doing libraries that can be used in any context)?
from csharplang.
Or maybe can you introduce a compiler flag to change the default value?
e.g. in .csproj
<TaskConfigureAwaitDefaultValue>False</TaskConfigureAwaitDefaultValue>
Then C# compiler compiles await task
as await task.ConfigureAwait(false)
from csharplang.
Hoping for a status update and an ETA.
A proposal without a champion on the language design team has no status or ETA. This project is opensource and as a result you are free to fork it and do anything you want, but being opensource doesn't mean that the team has to implement any proposed feature requests. Thus far this is deemed to not be a language issue. C#, as a language, has no concept of synchronization contexts. All proposals here want to tie await
to the TPL, which is undesirable.
from csharplang.
This project is opensource and as a result you are free to fork it and do anything you want, but being opensource doesn't mean that the team has to implement any proposed feature requests
Right, so, a good solution to an entire community begging for a solution is for everyone to just fork our common platform and do what we like?
I simply don't believe there are people out there that think ConfigureAwait(false)'ing every await is the right solution. I can't say which team within Microsoft should solve this, nor can I say which solution is the best, but I do see a major pain going unanswered for years and years. Surely, we can all agree that someone has to do something and that someone is within Microsoft.
from csharplang.
I haven't met a developer who doesn't want this fixed on an assembly/project level but arguing this doesn't help the issue.
🙋♂️ Hi! Now you have 😀
from csharplang.
It's kind of a shame that the AOP qualities of source generators ended up being rejected. If this API existed in the BCL (or could be written by a third-party), it seems like it would be trivial to have such a generator rewrite async members to call this API before invoking the actual method, e.g.:
public replace Task<T> FooAsync() {
using (Task.DisableSynchronization()) {
return original();
}
}
from csharplang.
@petertiedemann A good starting place is this recent document:
https://github.com/Microsoft/vs-threading/blob/master/doc/library_with_jtf.md
The basic idea is a library that avoids the use of ConfigureAwait
will execute code on the synchronization context(s) that appear at the entry points to the library API. If those entry points occur on resource-limited contexts (e.g. the single main thread), the caller is expected to use a deadlock mitigation strategy. If the library further uses JoinableTaskContext
internally (as opposed to just the implicit dependency from using ConfigureAwait(true)
), the caller is expected to use vs-threading as the mitigation strategy.
from csharplang.
Generators would address that.
from csharplang.
Please assign a milestone, this feature is awaited.ConfigureAwait(false) by all c# devs :)
Now we have await on almost each line of code. Do we still need to write ConfigureAwait(false) or use IL-rewriters mandatory?
from csharplang.
@alexk8 as a whole, solving the ConfigureAwait problem is earmarked for some future release, be it C# 9.0 or 10.0 or 11.0 etc. [source]
This particular solution, not so much.
from csharplang.
@theunrepentantgeek what if instead of an assembly-level flag, we add a class-level attribute? The attribute would make all methods of the class automatically wrapped inside ConfigureAwait(false) for all callers of the class.
from csharplang.
Currently this is a potential solution that I would be most ok with: #2649
But there is still a lot of design work ahead of us.
from csharplang.
the compiler gives warnings that we should be calling ConfigureAwait(false)
There are a lot of static analysis libraries, such as Roslynator that produce that specific warning. In Roslynator, it's RCS1090.
Chances are you're seeing that warning because you've opted-in to using a library that generates it.
If the warning isn't valid for your context, disable it.
from csharplang.
I don't quite understand why
CA(true)
, which assumes our desire to stay in the sync context, takes precedence overCA(false)
that doesn't care about it, which is applicable to most libraries as well as web projects
Because it is estimated that applications outnumber libraries by at least 10 to 1 and that if CA(false)
was the default that a significant percentage of more code would require CA(true)
than needs CA(false)
today. I would also make the argument that those application developers would also tend to be more junior developers who would be less familiar with the nuances of threading and synchronization and more likely to make mistakes if they had to be aware where they would need to apply CA(true)
.
from csharplang.
Keep in mind that the original ASP.NET app model also required app-level awaits to capture the synchronization context, and ASP.NET Core dropping that is a more recent development. Also unit tests sometimes require capturing the synchronization context, depending on the framework. Reusable helpers, libraries and (most, not all) console apps are the exception when they are framework-independent (agnostic to any app model).
from csharplang.
To put it simply and briefly, this belief is based on personal experience:
That's fine. But that's your personal experience. The idea of driving this off of data is to make a better default for the ecosystem as a whole, with the understanding that those that don't fall into the default may have a small amount of extra work.
You fall into the other group, so this happens. It's ok if that's the case. As I mentioned, working in the roslyn libs is a major part off my job. So I too have to deal with CA(false). But my development approach is simply too let automated tooling handle it for me, since it is fast and so simple to do :-)
I genuinely almost never even think about it. The only time I do are when I actually need to carefully use CA(true).
from csharplang.
ConfigureAwait(false) is an optimization, not a correctness issue. The incorrectness leading to the deadlock problem is in blocking the UI thread in the first place. This is true whether it's a call to task.Result
or some other form of blocking wait for something to change.
The separate problem of running CPU-heavy code on the UI thread is a problem that already exists with or without ConfigureAwait. The library might even have ConfigureAwait(false) and still end up bottlenecking the UI thread:
var x = await SomeIOStuffAsync().ConfigureAwait(false);
// On the UI thread still, if the previous await happens to complete synchronously
CpuHeavyStuff(x);
There various ways to assign responsibility for this, but ConfigureAwait(false) only makes this kind of problem more intermittent without solving it.
from csharplang.
@ashmind A quick question, does the following (from #114 comment) needs ConfigureAwait(false)
on everyawait
?
static async Task<TResult> UsingAsync<T, TResult>(this T disposable, Func<T, Task<TResult>> func)
where T : IAsyncDisposable
{
try { return await func(disposable); }
finally { await disposable.DisposeAsync(); }
}
assuming that it is in a library.
from csharplang.
@HaloFour But can it be implemented in CoreFX without option B support by compiler?
How would you know which assembly you are currently awaiting in?
from csharplang.
Considering how loose the awaiter spec allows for resolving the GetAwaiter
method I'm kind of surprised that it doesn't allow for the method to contain optional parameters today. I'd be game for allowing it, and of course I'm all for the expansion of the caller info attributes.
from csharplang.
@ufcpp pretty nice solution.
Why they didnt added something like that to the TPL?
from csharplang.
..or we can make .ConfigureAwait(false) a VS 2017 built-in keyboard shortcut.
That's actually possible. We could add ConfigureAwait(false);
to the completion list off the task.
So that would be C Enter.
from csharplang.
here's no reliable way I see that this could be implemented purely in library; it would need to be based in language support. As such, I'm going to close this out. Thanks for the interest.
from csharplang.
That's a proposal for an attribute, not a helper method. The failing of trying to manage that via an attribute is that it's far from obvious how the behavior is applied and when. A helper method that is invoked imperatively would not have that same issue.
from csharplang.
It would be great to have this - my code is polluted with lot of ConfigureAwait. Pleas do something with this – it should not be a big thing.
from csharplang.
Or maybe we can add a function-level attribute? For example:
[ConfigureAwait(false)]
public async Task FooBar() {...}
from csharplang.
we prefer a native solution, as modifying the IL doesn't seem like a reliable solution for us.
If there's a working solution... why not use it? :) Is there actually something unreliable here?
from csharplang.
@CyrusNajmabadi I have yet to see an IL rewriter that correctly preserves debugging information. Also EnC won't work if you IL rewrite compiler outputs. I generally do not recommend using IL rewriters.
from csharplang.
It's seems like a particularly onerous restriction to eliminate this entire class of tools. It basically means that all of that functionality must always be built into the compiler. That seems unfortunate.
from csharplang.
I simply don't believe there are people out there that think ConfigureAwait(false)'ing every await is the right solution.
The TPL optimized for library consumers, where synchronization is the more commonly necessary. If you're putting ConfigureAwait(false)
you're either doing it wrong, or you are only writing libraries and represent a minority of the development community. The majority of developers don't need to do anything.
Either way, C# doesn't know that ConfigureAwait
even exists. The "awaiter" pattern is completely synchronization agnostic. IMO, this is better solved via APIs in or on top of TPL.
from csharplang.
The majority of developers don't need to do anything.
Having worked with software development 12 years now, in multiple companies, having had hundreds of developer colleagues, doing all kinds of application types, I can't think of anyone who hasn't written to write a library at some point.
The number of times I had to help people debug something and found the problem to be missing ConfigureAwait(false) can't be counted using my fingers alone.
Either way, C# doesn't know that ConfigureAwait even exists. The "awaiter" pattern is completely synchronization agnostic. IMO, this is better solved via APIs in or on top of TPL.
I'm sure you are right. Which repo/Microsoft-team do I have to contact? Even better, do you know any specific people?
from csharplang.
It's just mind-blowing and very sad that this is still an issue in 2020.
No one has an adequate solution for this issue.
You want feedback, you want to be open source, you want a community, well then don't ignore a major plain like this one for years. I get that it's hard to solve and maybe it's falling between two chairs and so on, but it has been years now!
We do not have a solution that we find adequate for this problem. Wanting this badly doesn't change that.
but you need to fix this and you need to fix it now...
We don't have a solution that people think is adequate to fix the issue :-/
Hoping for a status update and an ETA.
There is no status update. We're in the same boat as before. Hopefully someone can come up with a solution that all stakeholders feel is suitable.
from csharplang.
Having worked with software development 12 years now, in multiple companies, having had hundreds of developer colleagues, doing all kinds of application types, I can't think of anyone who hasn't written to write a library at some point.
That's not what was said. What was said was:
The TPL optimized for library consumers, where synchronization is the more commonly necessary. If you're putting ConfigureAwait(false) you're either doing it wrong
The design is to optimize for the majority case at the expense of the minority. You're in the minority, and thus this is unpleasant for you.
from csharplang.
I simply don't believe there are people out there that think ConfigureAwait(false)'ing every await is the right solution.
Thinking that we don't hav ethe right solution doesn't magically make a right solution appear. There are tons of things i do not like, but i don't know how to solve them. So the status quo is the best state we know of.
I can't say which team within Microsoft should solve this, nor can I say which solution is the best, but I do see a major pain going unanswered for years and years. Surely, we can all agree that someone has to do something and that someone is within Microsoft.
Teams at MS have looked at this, and no one has been able to come up with a suitable improvement. If you have a proposal, please let us know! :)
from csharplang.
I'm sure you are right. Which repo/Microsoft-team do I have to contact? Even better, do you know any specific people?
I've proposed the following API that would temporarily reset the SynchronizationContext and TaskScheduler within a using
block:
That would apply to all methods called within that block so that it would only need to be used at the boundary of any libraries or other such code.
I'm not saying that this is the best answer, but it's a relatively easy API both to implement and use. I could see it being combined with analyzers that could help identify when you'd need to use it.
from csharplang.
But there is still a lot of design work ahead of us.
I already noted my many objections within the comments of that proposal. It's a shotgun and it will only result in very subtle bugs, assuming that it can survive design around the 5+ orthogonal features that would have to be individually shipped to enable it at all.
~20 or so LOC in the BCL would obviate the need for a language solution. Keep the concerns of the TPL to the TPL. That seems to me a better option than expecting everyone to have to copy&paste their own flavor of an await
operator within their own project and hope that they scoped it carefully enough to only affect the code they intend it to.
from csharplang.
Which is why I think the most correct answer is @CyrusNajmabadi's: We have no workable solution at this time. Creeping knowledge of how the BCL implements Task
into the language is not something anyone in the LDM is excited about. Having a solution in the runtime or framework that could be idiomatically exposed in the language would be best and imho and Stephen's suggestion is a step in that direction but it has a lot of design issues (a lot) that would need to be worked out. I can see a path forward in #2649 but I do think there are lots and lots of usability and design questions that may kill it.
from csharplang.
Right now there is a default behavior, by not calling ConfigureAwait at all. But if we use that, the compiler gives warnings that we should be calling ConfigureAwait(false) to override that default behavior. There seems to be a disconnect there, with a default behavior that is also a non desirable one.
from csharplang.
But if we use that, the compiler gives warnings that we should be calling ConfigureAwait(false)
There is no such compiler warning.
from csharplang.
with a default behavior that is also a non desirable one.
It's desirable to me much of the time. When i'm doing libraries it isn't. But for non-lib code, it works very nicely.
from csharplang.
Ok not a compiler warning exactly, a warning from the code analyzer. The default works for me too and I dismiss the warning. If you don’t include that analyzer, visual studio gives you a huge warning banner that you should be including it. Not a big deal, I can turn off this specific warning in the analyzer, though still feels like there’s a disconnect when you have the analyzer telling you that the default behavior is wrong and you should override it every single time.
from csharplang.
Not a big deal, I can turn off this specific warning in the analyzer, though still feels like there’s a disconnect when you have the analyzer telling you that the default behavior is wrong and you should override it every single time.
You should file an issue with that analyzer. This is not the repo for that. We do not control the behaviors and decisions that individual analyzer packages decide on.
from csharplang.
I've seen (relatively few) problems going unsolved for years
I mean... there are literally several thousand issues in this repo. Most of them are multi-year long :) So it's the norm that nearly all issues and concerns raised are just not going to be solved (possibly ever).
I don't know if Microsoft has any cross-team meetings to look at "over X-year-old" issues,
Yes. The teams meet and discuss what they think warrants attention. This has not risen to the level of outweighing the other stuff that is our current priority
If there really is no good solution, not even one that can be defined by a cross-team effort, then that's fine, but then close the issues with proposals you can't/won't implement. Then there is at least some "progress" and a "result".
I don't see a need to close this. Perhaps something will come of it in the future. I don't want to stifle people's ability to communicate and discuss this issue, or to discuss the pros and cons of this particular proposal :)
and make a community-wide contest out of it or something, just to let it be known that there is a hard problem that will go unsolved unless something extraordinary is done about it (just a suggestion from a frustrated developer).
I don't really see what about this issue warrants that. This is just another issue. To some it may be super impactful, to otehrs it may not show up on the radar at all. I don't think we want to just take the backlog of hundreds/thousands of bugs and just say "hey... we're running a contents to find solutions" just because some people really find the current state of affairs highly unpleasant. :-/
Like i said before, i think there's a perfectly reasonable solution. @HaloFour has already showed what it is. Given a reasonable solution, and no actual language-proposals that i think are really great, i know that this isn't an area i would champion to move forward.
TBH, @blankensteiner, it's not clear to me why you just taking the approach i mention here would not be suitable.
from csharplang.
I don't really see what about this issue warrants that. This is just another issue. To some it may be super impactful, to otehrs it may not show up on the radar at all
I doubt that. What you are seeing here is feedback from those who don't mind voicing their frustration. Do you really know the number of developers who want this fixed? The number of those swallowing the fact that they have to use ConfigureAwait.Fody or manually setting CAF?
I haven't met a developer who doesn't want this fixed on an assembly/project level but arguing this doesn't help the issue.
TBH, @blankensteiner, it's not clear to me why you just taking the approach i mention here would not be suitable.
It's a fine quick fix until a real solution is found. One where it's a switch/compile-setting or something that doesn't mean I have to insert some code everywhere.
This is my two cents to this discussions. If you feel that this issue is being given the attention it deserves, then let's stop the conversation here and just see when/how/if this is solved.
Have a nice weekend.
from csharplang.
🙋♂️ Hi! Now you have 😀
April fools day arrived early this year :-)
from csharplang.
I'm serious. One more subtle invisible thing to mentally track is not thrilling. It is what it is, and it's not even that bad. It takes more typing to propagate cancellation tokens.
from csharplang.
I haven't met a developer who doesn't want this fixed on an assembly/project level but arguing this doesn't help the issue.
Shutting down part of the argument because you don't like that perspective is not helpful. Yes, the problem becomes easy if we no longer have to be concerned about the things you don't care about. However, that's not how we can actually do the design.
I, for one, am absolutely not ok with a ambient switch changing the runtime semantics of my code. We have that in an another place in the language (present in 1.0), and it's viewed as a painful mistake we don't want to repeat.
from csharplang.
It's a fine quick fix until a real solution is found.
What makes it not "a real solution".
It's simple, effective, and totally clear in the code.
from csharplang.
Inspired by @HaloFour : ASP.NET/Core allows Action Filter attribute to add logic before and after a function implementation. Python attribute also enables that. And C# does have attributes affecting function behavior, e.g. force-inline, but not adding logic.
Of course, an AOP rewriter can detect an attribute and rewrite the code from:
[DisableSynchronizationContext]
public async Task<T> FooAsync() {
await BarAsync();
}
To
public async Task<T> FooAsync() {
using var _ = Task.DisableSynchronizationContext();
await BarAsync();
}
from csharplang.
I stumbled upon this right after seeing the announcement for source generators earlier today.
Introducing C# Source Generators | .NET Blog
Wonder if they could be the key to make ConfigureAwait(false)
the default.
from csharplang.
I don't think source generators can be used to accomplish that. The generator can only add new source, it can't modify existing source.
from csharplang.
Ah, I didn't know about that. Bummer! Oh well… 😐
And you're totally right. I missed that bit from their FAQ: (emphasis mine)
How do Source Generators compare to other metaprogramming features like macros or compiler plugins?
Source Generators are a form of metaprogramming, so it’s natural to compare them to similar features in other languages like macros. The key difference is that Source Generators don’t allow you rewrite user code. We view this limitation as a significant benefit, since it keeps user code predictable with respect to what it actually does at runtime. We recognize that rewriting user code is a very powerful feature, but we’re unlikely to enable Source Generators to do that.
from csharplang.
If the generator could create methods that overwrite existing ones, then we could create something that wraps every Task
function with ConfigureAwait(false)
.
from csharplang.
Generators can't overwrite existing code. If the generator did emit a duplicate method that would only result in a compiler error because you'd have two methods with the same signature.
from csharplang.
As for generators, I'd rather opt out for some standard solution (i.e. something that's supported by C# compiler), otherwise we'll end up with a plethora of non-standard ones here, which will make the problem even worse.
from csharplang.
Top most evil villains in history:
- That guy who thought
.ConfigureAwait(true)
by default would be a great idea
from csharplang.
That guy who thought .ConfigureAwait(true) by default would be a great idea
I prefer CA(true) as the default almost universally for all my personal work. It's ideal. For narrower cases, CA(false) is more appropriate. But it's for a more complex domain where i need to think about those sorts of subtleties.
CA(false) being the default woudl be utterly broken for some many normal and simple cases.
from csharplang.
I prefer CA(true) as the default almost universally for all my personal work
@CyrusNajmabadi, can you add some examples, please (if it's not very time-consuming for you)? Once you've already convinced me on one issue, maybe here you'll make some good points too :)
As for now, I only see a bunch of frustrated developers (myself included) running around their projects putting out CA calls. I don't quite understand why CA(true)
, which assumes our desire to stay in the sync context, takes precedence over CA(false)
that doesn't care about it, which is applicable to most libraries as well as web projects
from csharplang.
Because it is estimated that applications outnumber libraries by at least 10 to 1
@HaloFour, is it really? 0_o Wow. Even at the glory days of the .NET Framework, I would hardly believe in such a ratio, but at the present time, it seemed to me that the number of libraries should certainly exceed the number of applications that depend on the synchronization context... Could you share a link to the research papers, please? I can't find any reliable source, but it seems really interesting
I would also make the argument that those application developers would also tend to be more junior developers
Not even a question, true for most of the cases that I personally encountered
Well, if 10 to 1 ratio is real, we, library-writers, are the ones to suffer (if not suffer at all is not even on option :))
from csharplang.
I don't quite understand why CA(true), which assumes our desire to stay in the sync context, takes precedence over CA(false) that doesn't care about it, which is applicable to most libraries as well as web projects
The answer lies in the history of the async/await keywords in C# - they were released in C#5 in 2012. The developers working on this stuff were doing so in 2011 (and earlier), a decade ago.
One of the motivations for the asynchrony feature (well, one of the primary ways it was marketed and demonstrated, anyway) was that it made it dramatically simpler to avoid user interfaces that locked up when developing applications using WinForms and WPF.
Both of those user interface frameworks have a baked in assumption that you only interact with UI elements from the exact same thread that initially created them. This rule is inviolable - break it and your application will crash, hard.
The libraries built to support asynchrony therefore ensured that execution returned to the original thread when tasks completed - to do anything else would have made the feature difficult to use to the point of being useless.
from csharplang.
Well, if 10 to 1 ratio is real, we, library-writers, are the ones to suffer
Besides what others have said:
- The only library authors who suffer from this is those who create cross-framework libraries which can be used in apps of different sync contexts. If your library is for a single framework, e.g. WinFrom, you know the target sync context so you simply stick with default behavior, and only
ConfigureAwait(false)
when necessary. For newer frameworks like ASP.NET Core, you no longer need to bother with it. - As long as your library is intended for different sync contexts, you must know the sync context for each use case.
ConfigureAwait(false)
may crash the consumer app, whileConfigureAwait(true)
may deadlock it. Neither is safe default. - Having class library projects in an app solution still counts as app here, because they are specific for the app's choice of sync context.
from csharplang.
@CyrusNajmabadi, can you add some examples
Every application i could write would prefer this. if i asynchronously handle something, i want to return back to the UI thread to go continue interacting with it.
from csharplang.
Keep in mind that the original ASP.NET app model also required app-level awaits to capture the synchronization context, and ASP.NET Core dropping that is a more recent development. Also unit tests sometimes require capturing the synchronization context, depending on the framework. Reusable helpers, libraries and (most, not all) console apps are the exception when they are framework-independent (agnostic to any app model)
@jnm2, I do keep this in mind, but that doesn't change in any way that the ratio of 10 to 1 is surprising to me, as there's no simple logical outcome that makes "reusable helpers, libraries and (most, not all) console apps" an exception and makes context-dependent apps the rule, and not vice versa. No matter how you say this, Libraries-to-Apps
is still Many-to-Many
One of the motivations for the asynchrony feature (well, one of the primary ways it was marketed and demonstrated, anyway) was that it made it dramatically simpler to avoid user interfaces that locked up when developing applications using WinForms and WPF
@theunrepentantgeek, well, yeah, I understand the part of the argument about WPF, WinForms, maybe even Silverlight (I stopped using it way before async/await, but it was still alive back in the days, as I remember it clearly), but this approach kinda makes assumption that C# has no future outside of Windows*
Like, ok, nowadays a lot of code is sync context-sensitive (at the end of the day it will be consumed by either ASP.NET, WinForms, WPF or Silverlight anyway. Console apps are an exception, as their number is definitely smaller compared to other applications, I agree), but what about the future? OS-free environment encourages the development of general purpose libraries, so even if apps outnumber libraries now*, it will change in the future*
Have you heard of Dark Mater Developers?
Interesting article to read and to think about! :)
So, you assume that "dark matter developers" only (or mostly) develop applications. What about corporate libraries? Reusable code? And so on
The only library authors who suffer from this is those who create cross-framework libraries
@qrli, you said that like they're a minority :)
I've indexed all 325626
(I don't know for sure why this number doesn't match one on the main page) libraries on the https://nuget.org (I do hope they won't blacklist my IP for this small DDoS xD), excluded from them all that depend or may depend on some context-sensitive framework (like WPF, ASP.NET and so on), and received 282141
libraries in the output. So roughly 85%
of the libraries are context independent
As long as your library is intended for different sync contexts, you must know the sync context for each use case.
ConfigureAwait(false)
may crash the consumer app, whileConfigureAwait(true)
may deadlock it. Neither is safe default
I’m having trouble parsing "is intended for different sync contexts". Do you mean some GUI library, that targets several frameworks like WPF/WinForms at the same time, or some general purpose library?
If it's first, then yeh, maybe.
If it's second, then I can't imagine such a scenario.
Having class library projects in an app solution still counts as app here, because they are specific for the app's choice of sync context.
Not true at all. My latest Avalonia app consists of 14 projects. And only 1 is context dependent - the app itself. Even VM project was built in such a way that it doesn't rely on the sync context
Every application i could write would prefer this. if i asynchronously handle something, i want to return back to the UI thread to go continue interacting with it.
@CyrusNajmabadi, what about libraries, if you write any?*
*P.S. - These statements are written on behalf of someone who believes there're more libraries than applications. You, of course, have shaken my confidence about this a little bit, but I've not found any verified calculations, so this issue remains in the category of "religion", not facts. At the end of the day, this ratio plays a role only in the question of whether .ConfigureAwait(true)
as default was chosen correctly. And no matter how we answer it today, the problem still remains for a nonzero number of people, and it needs to be somehow solved
from csharplang.
.NET and C# teams did at the time based on feedback and VS telemetry.
What feedback s and telemetry can be on functionality that did not exist at that time?!
And why it was decided to do exactly
ConfigureAwait(false)
but not with something
await? / await! / ^await etc
from csharplang.
Nowadays it is not required to use Task - it would be possible to create another awaitable object with other default behaviour. This could be implemented in the runtime or a regular user library - similar to ValueTask. And it could be implicit convertible to and from Task....
from csharplang.
Solutions that make the lives better for library developers would certainly be entertained, although it's a steep hill to climb to get the language designers on board that TPL-specific policy should be something baked into the language. API solutions have been better received, such as the ability to clear the synchronization context for a specific scope, which can be applied at the library entry points and eliminate the need to call CA(false)
at every other await
. Something like this.
As for libraries v. applications, those who make the decisions had the data to decide which audience should have the optimized experience. You have your own anecdotal evidence that contradicts those decisions, and that's fine, but even if the team wanted to revisit it they couldn't without breaking massive amounts of code. And I would bet money that the telemetry still bears out that the majority of applications are internal WinForm business apps, by a very large margin.
from csharplang.
In longer terms, you forgot one thing about libraries: they're the logical unit just as functions or classes. If a function becomes too large, we split it into several ones. Small functions are easier to maintain, easier to debug, easier to test (we don't have the complexity index for no reason). The same applies to libraries. Their goal is far from just reusing some code.
IMO, If you're using libraries specifically for the purpose of organizing code that would have otherwise lived in the application itself then the guidelines of adhering to CA(false)
need not apply. This is also true for libraries that are reused across very similar applications. It's not the project type that matters, it's where that assembly is used and the threading restrictions with which you need to be concerned. If all you write is ASP.NET Core apps and your libraries exist to service those apps then forget that CA(true/false)
exists.
Some will likely disagree with me on these points out of sheer abundance of caution, and they're not wrong. But IMO you shouldn't need to change the code you write only by virtue of where that code lives.
from csharplang.
Solutions that make the lives better for library developers would certainly be entertained
And that's the good news
Something like this
I've seen this proposal before, and it's certainly better than the current order of things, but it's still not the real solution: we're replacing thousands of boilerplate-affected lines of code with hundreds of boilerplate-only lines of code
Well, about API changes: some people have previously proposed a new class that behaves the same as task.ConfigureDefault(false)
. Maybe this is the solution? It doesn't break existing code, it doesn't require new language features and there's no boilerplate involved.
async ContextlessTask Foo()
{
await Task.Delay(30).ConfigureAwait(true);
LetsAccessSomeUI(); // Ok
await Task.Delay(30);
// LetsAccessSomeUI(); // Exception
}
async Task Bar()
{
await Foo();
LetsAccessSomeUI(); // Ok
await Foo().ConfigureAwait(false);
// LetsAccessSomeUI(); // Exception
}
Which downsides it may have?
but even if the team wanted to revisit it they couldn't without breaking massive amounts of code
Nobody asks to reevaluate that decision (I hope), of course not. Breaking changes that don't make the impossible possible aren't worth it ;)
Some will likely disagree with me on these points out of sheer abundance of caution, and they're not wrong. But IMO you shouldn't need to change the code you write only by virtue of where that code lives.
I am the type of person who approaches the task in a more abstract way, so I assume that the library will be reused even if it's very likely it won't (but it often pays off). But it's fair point indeed :)
from csharplang.
If all you write is ASP.NET Core apps and your libraries exist to service those apps then forget that CA(true/false) exists.
I’ve heard ConfigureAwait(false) is no longer necessary in .NET Core. True?
So to say that ConfigureAwait (false) is only needed in libraries is a bit presumptuous
from csharplang.
I mentioned ASP.NET Core specifically, which doesn't use a SynchronizationContext
and where ConfigureAwait(false)
is basically a no-op as a result. If your libraries only ever run in such an environment then you don't need CA(false)
. If you're using .NET Core with WinForms or other UI frameworks then you might want CA(false)
.
I also think that @jnm2 makes an excellent point that CA(false)
is really more an optimization. There are scenarios where you want to try to avoid resuming the coroutine on the UI thread, but it's almost never a requirement. Deadlocks would only occur if the code blocks, and that's already possible in UI apps that block or loop expecting a signal to happen that must be marshaled to the UI thread. Otherwise it's largely about trying to keep processing off of the UI thread so that it doesn't impact the responsiveness of the UI, and if that isn't a concern for your code then you don't really need to worry about it so much.
from csharplang.
If your libraries only ever run in such an environment then you don't need CA(false)
It doesn’t mean, however, that there will never be a custom SynchronizationContext or TaskScheduler present. If some user code (or other library code your app is using) sets a custom context and calls your code, or invokes your code in a Task scheduled to a custom TaskScheduler, then even in ASP.NET Core your awaits may see a non-default context or scheduler that would lead you to want to use ConfigureAwait(false).
I am not a fortuneteller.
Even asp.net core project can consist of several modules, the future of which is unknown
I want to make sure there won't be a situation where in the future someone will use my class and get a deadlock because I assumed it would never happen. Therefore, I always explicitly use ConfigureAwait(false) everywhere to be sure that my code works in any environment as expected.
from csharplang.
Therefore, I always explicitly use ConfigureAwait(false) everywhere to be sure that my code works in any environment as expected.
I do the same for the libraries i write. That said, i don't explicitly write it myself. Or, at least, i don't try to write out every single one of these. I just use the quickfix to apply it to the entire doc at once:
from csharplang.
Related Issues (20)
- [Proposal]: Collection Expressions Next (C#13 and beyond) HOT 47
- Open issues: Breaking changes HOT 29
- [Proposal]: Field and value as contextual keywords HOT 33
- [Proposal]: Extended identifier syntax
- [API Proposal]: IsNullableAttribute to flow nullability of generic type parameters into methods HOT 4
- Anonymous type optimization HOT 5
- Feature Request: Recursively Foreach
- [Proposal]: Relax `Add` requirement for collection expression conversions to types implementing `IEnumerable` HOT 2
- [Proposal]: Proposal for Allowing Single-Element Tuples in Type Definitions HOT 2
- [Proposal]: Params collections and older language versions HOT 1
- [Proposal]: Edit speclets to distinguish features already delivered from potential future enhancements HOT 3
- [Proposal]: `readonly` parameters in primary constructors for non-record types HOT 7
- [Issue]: Generic Type Specialization Not Working in C# HOT 38
- [Proposal]: AAAAAAAAAAAAAAAAAAAA
- Open questions for `field` and `value` as contextual keywords HOT 2
- Add collection literal support for `Memory<T>` and `ReadOnlyMemory<T>`. HOT 18
- Unexpected compiler behavior with records and implicit operators HOT 1
- YaoJunFeng
- Invalid implicit conversion from long to int HOT 6
- [Proposal]: Extending patterns to "as" HOT 19
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from csharplang.