dotnet / csharplang

The official repo for the design of the C# programming language
11.08k stars 1.01k forks source link

[Proposal] Forcing ConfigureAwait(false) on assembly level #2542

Open ashmind opened 8 years ago

ashmind commented 8 years ago

Problem

It's recommended to always use ConfigureAwait(false) in certain kinds of libraries. While it's definitely possible to use an analyzer (e.g. ConfigureAwaitChecker.Analyzer) to catch those cases, the analyzer has to be installed separately and the resulting code is awkward and verbose.

Potential solutions

Option A

Provide an assembly-level attribute that would force compiler to generate (pattern-based) ConfigureAwait(false) calls for each await.

Option B
  1. Implement CallerMethodAttribute from #351
  2. Add support for method info attributes to pattern-based GetAwaiter calls
  3. This would allow for a new overload GetAwaiter([CallerMethod] MethodBase method = null)
  4. Task could use this overload to look for some attribute on method.Assembly and return a correspondingly preconfigured awaiter.
vitidev commented 3 years ago

@CyrusNajmabadi

And how is it better than adding code with code completion? It's exactly the same. For example, ConfigureAwait.Fody writes instead of us, but you just have a variation of coding by hand.

I use ConfigureAwait.Fody in pet projects and I only have to add ConfigureAvail (true) in rare places (and I'm not talking about library code) - and this is very convenient, but ConfigureAwait.Fody is not oficial way

CyrusNajmabadi commented 3 years ago

For example, ConfigureAwait.Fody writes instead of us, but you just have a variation of coding by hand.

Both sound reasonable to me.

but ConfigureAwait.Fody is not oficial way

As i said above, this doesn't rise to the level that an official way is necessary. I don't view the problem as significant enough to warrant anything beyond the current story. If we had a really good solution here, i'd consider it. But we're in teh state of:

  1. not something that really feels like a big problem
  2. no good solutions readily apparent

Given both of those. My position is to keep the status quo and leave this to the tooling space.

vitidev commented 3 years ago

I don't view the problem as significant enough to warrant anything beyond the current story.

Just deadlock because of the forgotten ConfigureAwait (false) somewhere in the depths of tons of method calls ) Because someone decided that deadlock is better than "access from another thread" even though it is much more difficult to find the reason for the deadlock . In the case of "access from another thread", the reason is always easy to find - the last await. For deadlock - we have to go through all methods chain to find the place of the forgotten ConfigureAwait

CyrusNajmabadi commented 3 years ago

Just deadlock because of the forgotten

I never forget CA. That's what that analyzer is for. I don't need to remember/forget anything. The tools take care of this for me. That's why i view this better as a tooling problem

Because someone decided that deadlock is bette

We decided that it's much saner and meets the majority case to come back to the callign context. My own experience here tells me that this is appropriate and desirable :)

jnm2 commented 3 years ago

Deadlock is not the fault of a missing ConfigureAwait(false). Sometimes ConfigureAwait(true) is actually needed, and then what? It's the fault of code that decided to block the UI thread until something changes. The UI thread should never be blocked.

vladd commented 3 years ago

Just adding my $0.02, the solution with await ThreadPool.SwitchTo(); (discussed here, akin to this one mentioned here), works like a charm. Having one extra line per method is a low price, and makes the programmer's intention very explicit.

I'd prefer the solution without an explicit way to restore the original context in order to not endorse "an unstructured/unscoped way of hopping between contexts" mentioned here: the switch to the thread pool should typically happen once at the beginning of a public method.

vitidev commented 3 years ago

@CyrusNajmabadi

I never forget CA. That's what that analyzer is for.

How does the analyzer distinguish a forgotten ConfigureAwait from "it should be by default"? So you always write ConfigureAwait explicitly. That is, you litter your code with numerous ConfigureAwait

The problem is not "who write ConfigureAwait" . The problem is the need to write a bunch of boilerplate code at all, which worsens readability

@jnm2

Sometimes ConfigureAwait(true) is actually needed, and then what?

If this is the UI context, then there is nothing to talk about.

Non-UI context should not assume that he is "alone in the universe" Why do we use ConfigureAwait(false) everywhere? Because we assume that we are being called in some kind of synchronization context and we want to avoid its influence. Having your own context doesn't change anything. We must always assume the presence of an external context, if we do not UI

HaloFour commented 3 years ago

@vitidev

The problem is not "who write ConfigureAwait" . The problem is the need to write a bunch of boilerplate code at all, which worsens readability

It sounds very much like the problem is "who write ConfigureAwait". Someone needs to write it, because there are two options as to what to do and the intent must be stated. When the decision is whether to make developers write it 90% of the time or 10% of the time the decision is obvious and clear.

Shrinking that down to a single character embedded in the await operator doesn't remove that boilerplate and certainly doesn't improve readability. It would make it a lot easier to miss. But if your aim is to make it fewer characters, then write your own extension method that calls ConfigureAwait(false). You could get it down to a total of 4 extra keystrokes that way.

Because we assume that we are being called in some kind of synchronization context and we want to avoid its influence.

It's your decision to take that burden onto yourself. If those libraries are a part of a solution for an application it's not very likely that the library code will be called from under multiple contexts. It's also not particularly likely that it actually matters whether or not that library code executes within that context. If that context needs to marshal information but not jump threads then it matters even less.

This discussion is going in circles and there clearly isn't an answer that will satisfy everyone. Short of proposing actual changes it doesn't seem there is really anywhere for this to go.

vitidev commented 3 years ago

certainly doesn't improve readability.

Does removing ConfigureAwait not improve readability? Seriously? That's why there is a default ConfigureAwait(true), and not "force everyone to specify ConfigureAwait explicitly" because it improves readability.

then write your own extension method

I can write anything. I can even use Fody.ConfigureAwait . But I can only do this in pet projects.

It's your decision to take that burden onto yourself. If those libraries are a part of a solution for an application it's not very likely that the library code will be called from under multiple contexts.

It's just common sense not to predict the future. There should always be a clear contract with no side effects. Long-lived projects are regularly refactorings and any code can be moved in the future to a separate library(module) and reused in a context unknown at the time of code creation.

Also ConfigureAwait (true) is a little but useless overhead

HaloFour commented 3 years ago

Does removing ConfigureAwait not improve readability?

No.

That's why there is a default ConfigureAwait(true), and not "force everyone to specify ConfigureAwait explicitly" because it improves readability.

That would be because it is the majority use case. There's no need to require the developer to declare their intent to perform the default behavior. This has nothing to do with readability, it has to do with the pit of success.

I can write anything. I can even use Fody.ConfigureAwait . But I can only do this in pet projects.

That doesn't sound like a technical problem.

It's just common sense not to predict the future. There should always be a clear contract with no side effects. Long-lived projects are regularly refactorings and any code can be moved in the future to a separate library(module) and reused in a context unknown at the time of code creation.

The question would be how realistically that situation will occur for any given project, and it's likely not very high. Using a tool to add in CA(false) at the time this project is refactored would be entirely reasonable and take all of 5 seconds.

Luiz-Monad commented 3 years ago

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.

The only language that solved this problem of contexts in a sensible way is Kotlin. In Kotlin you are kind of forced to use contexts everywhere explicitly, but you set them on the function type. Which is not different from one of the proposed solutions in 2017. Which is having a ContextFreeTask type that does the correct thing.

F# doesn't have this problem because it has its own Async. I just stopped using C# because of it, its so much better. I just came for this discussion to fix an old C# powershell CmdLet, I guess I'll rewrite it on F# then.

This is how F# does it . https://github.com/dotnet/fsharp/blob/19ade780d6019e05c24e09dda145447760e258ba/src/fsharp/FSharp.Core/async.fs#L1665 example of using https://stackoverflow.com/questions/5433397/f-purpose-of-switchtothreadpool-just-before-async-return

95% of the time you don't want to bother with the Thread code runs, so ConfigureAwait default should be changed to false by the means of a massive breaking change to the TaskParallelLibrary, I mean, there should be two versions of TPL, one for UI, and the other for all other uses.

We really need a UITask that does ConfigureAwait(true) and the normal Task does ConfigureAwait(false), and it is a breaking change in so far as the user has to change its use of Task to UITask. As you do when you use Kotlin, for example (regardless of their generators being called suspend).

Luiz-Monad commented 3 years ago

@HaloFour

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.

Meh

There's a reliable way. Have two different Tasks, we already have ValueTask now. People should learn to have more kinds of "monads".

cafenroy commented 2 years ago

I suggest to introduce a new Keyword in the language ==> await2. That keywork would set .ConfigureAwait(false);

And at the project level, an option ==> ForceAwait2

jnm2 commented 2 years ago

bwait

jeme commented 2 years ago

I would also certainly love to see something akin to [assembly:TaskConfigureAwait(false)] as a solution, that would be backwards compatible and ease the pain greatly.

Library authors could apply that and those of us who would actually prefer to be explicit about needed the continuation to run on the same context/tread where it is needed, could just apply that to all that we do.

I am very curious about some of the all the defense made for the default chosen behavior here, to me it's one of the biggest design flaws of the TPL, and I certainly don't buy the "most people would be best off with the current default", not then, not now...

@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:

Hopefully though, most of those 80%-90% was working on something bigger than just a small little "All code in the GUI" application, and if so, then suddenly they where actually developing internal libraries. Even though a library is not shared in the world, and even if it's never shared in your organisation, doesn't mean you shouldn't treat it well.

And in terms of old patterns, why would I not want my BusinessLogic or DataAccess code to execute on the first ready and available thread? Why would I at all want to lock my application to only ever execute on the GUI thread in any other places than where it was absolutely needed? - That just doesn't make any sense to me. We jumped hoops before to offload work to other threads than the GUI, utilizing the CPU's cores as best we could.

So it's not about what type of developer there are most of, it's about what type of code there is most of to me, in the context of a windows forms application, if your code does not modify any form controls, draw on any surfaces etc. It's NOT UI code, and therefore should not require to execute on your UI Context, instead it should align to the recommendation for libraries, hence applying .ConfigureAwait(false) to all such code makes perfect sense to me.

Take this small, simple, dumb - but illustrative example.

using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AsyncFormTests
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new DumbForm());
        }
    }

    public class DumbForm : Form
    {
        private readonly IContainer components = null;
        private readonly Button theButton;
        private readonly TextBox randomTextBox;

        public DumbForm()
        {
            theButton = new Button();
            randomTextBox = new TextBox();
            SuspendLayout();

            theButton.Location = new Point(5, 5);
            theButton.Size = new Size(160, 50);
            theButton.Text = "ClickMe";
            theButton.UseVisualStyleBackColor = true;
            theButton.Click += OnClick;

            randomTextBox.Location = new Point(170, 5);
            randomTextBox.Multiline = true;
            randomTextBox.Size = new Size(260, 50);
            // 
            // Form1
            // 
            AutoScaleDimensions = new SizeF(6F, 13F);
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(440, 60);
            Controls.Add(randomTextBox);
            Controls.Add(theButton);
            ResumeLayout(false);
            PerformLayout();
        }

        private async void OnClick(object sender, EventArgs e)
        {
            theButton.Text = await MyBusinessLogic.DoHardWork();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
    }

    public class MyBusinessLogic
    {
        private static int count;

        public static async Task<string> DoHardWork()
        {
            await Task.Run(() => { /* Normaly we would not have Task.Run here, instead it would be: Run a DB Query, read a file, request something over the network */ })
                .ConfigureAwait(false); //Remove this, now the UI locks.

            //Now lets do some intensive work on that!!!
            DateTime start = DateTime.Now;
            while ((DateTime.Now - start) < TimeSpan.FromSeconds(5)) { }
            return $"COUNT: {++count}";
        }
    }
}

In that example, why would I not want to add that ".ConfigureAwait(false)"?, what would the need be?

I would much rather have had to do:

        private async void OnClick(object sender, EventArgs e)
        {
            theButton.Text = await MyBusinessLogic.DoHardWork().ContiniueOnThisContext();
        }

Because then it's explicit to me. This is the piece of code that has a special requirement, not the DoHardWork method, The DoHardWork method doesn't care if it's executed on the GUI thread or not:

Therefore, DoHardWork should not need to add anything special like ConfigureAwait(false). OnClick on the other hand...

Hence... it should be here I mark that I have a special requirement, namely that i require this piece of code to execute on the GUI Thread.

That also means that it's no longer "MAGIC", And in clearly signals that there is a special case/requirement here.

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, while ConfigureAwait(true) may deadlock it. Neither is safe default.

The standing recommendation from microsoft on this matter is that library authors should probably always use ConfigureAwait(false) - so I am very eager to get some examples to support the above statement, which to me seems to go against the recommendations.

Besides, any library you use provides you with a "contract" (the contract is often implicit though, so you have to discover it), if you don't uphold that contract and it means your application crashes, well then that is on you. So if a library applies ConfigureAwait(false) to all it's await and that somehow breaks your application, then that is not really the libraries problem, it's yours. But I am still VERY curious to see some examples of this in this particular situation. And even if there is some, I am sure they are easily fixed from the consumer's perspective.

Now... If we talk about flipping the "default" behavior for all Tasks tomorrow, then obviously it would be a massive breaking change that will break applications all over the world. There is no doubt about that, that is why I think that introducing a [assembly:TaskConfigureAwait(false)] attribute that ONLY flips the default for awaits in codepath's inside that assembly is a reasonable way to go.

Where as solutions like ThreadPool.MakeDefaultConfigureAwait(false); is obviously a big NO GO as that would affect code that might be based on the old default. E.g. if you consume a Form Controls library that uses the TPL on some places.

The important thing is that whatever is done to ease this, is something that only affects code "in your control".

Sorry if this became a bit "ranty" .

GSPP commented 2 years ago

@jeme You could take this argument even further: Should there be a SynchronizationContext at all? Should there be any ambient context at all (ExecutionContext, TaskScheduler.Current, TransactionScope.Current, any kind of thread affinity, ...).

These things are super dangerous. They make code non-local, non-composable and the bugs are nasty.

But they are so convenient... I'm really undecided on this. I sometimes think that these contexts optimize for novice programmers and demo-ware in which the goal is to make things "just work". The more scale the codebase has the more it requires preciseness and modularity.

Creating a ConfigureAwait(false) default sure is possible. The attribute could use a new C# language feature that allows wrapping an await expression in an arbitrary other expression. The C# language would remain neutral and just provide the infrastructure.

Luiz-Monad commented 2 years ago

I would also certainly love to see something akin to [assembly:TaskConfigureAwait(false)] as a solution, that would be backwards compatible and ease the pain greatly.

Library authors could apply that and those of us who would actually prefer to be explicit about needed the continuation to run on the same context/tread where it is needed, could just apply that to all that we do.

An interesting approach would be to use a different version of the TPL library that always do that. There could be two versions of the same library sharing the same contract and you could choose between UI/Library by simply adding the correct reference to your project. That would mean the context in which the configuration applies is the Assembly and the entire compilation and its pretty obvious which is which. An example would be breaking part of the interface of the Task into TPL.Pool.dll and TPL.UI.dll in which both use a TPL.dll assembly with the common shared code.

I am very curious about some of the all the defense made for the default chosen behavior here, to me it's one of the biggest design flaws of the TPL, and I certainly don't buy the "most people would be best off with the current default", not then, not now...

Certainly, that totally was a design flaw. But asking for breaking changes in working code is a obvious no go. So that's why I ask for another new incompatible and potentially different version of the TPL that does the correct thing. I'm presuming that we would have to recompile all source code of anything that uses Task either way with such breaking change.

Because then it's explicit to me. This is the piece of code that has a special requirement, not the DoHardWork method, The DoHardWork method doesn't care if it's executed on the GUI thread or not:

Seconded, explicit is always better.

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, while ConfigureAwait(true) may deadlock it. Neither is safe default.

Perhaps one could create a Roslyn analyzer/rewriter. On the library side, It would to detect all uses of Task with CA(false) and remove them and update the assembly of the TPL to the new assembly (for ex TPL_Pool ) which has CA(false) as default, if there was any CA(true) it will leave it. That shouldn't alter any behavior nor break anything, except it requires recompilation of the updated code. Then on the UI project it would do the opposite, all uses of any Task that was from the previous referenced libraries are going to be injected CA(true) and you would use another assembly (aka, TPL_UI) with an UITask instead, so it would be very explicit and a compile time error not to convert between Task and UITask using CA(true) (or something else better named). That wouldn't alter any behavior that wasn't already incorrect, nor break anything that wasn't already broken. Its good overall, and also requires recompilation. And for those who don't have the source code to recompile, someone could come up with a Mono.Cecil rewriter that does the same at CIL level and rewrite the binaries, or just such it up on the boundary by using CA(true) everywhere to convert between TPL_Pool.Task and TPL.Task or TPL.Task and TPL_UI.Task.

The standing recommendation from microsoft on this matter is that library authors should probably always use ConfigureAwait(false) - so I am very eager to get some examples to support the above statement, which to me seems to go against the recommendations.

I bet if some library is fooling with this, it would be better having its own Task library and Task Pool than using the shared one, in this case my solution would not work that well . Perhaps using the old EAP (Event-based Asynchronous Pattern) to this bullshit library that used TPL and somehow break if not running on tasks in the thread pool would be a good way to make them compatible. I've only seem one software that was doing it, it was the Microsoft RDL report designer and Crystal Reports both with where doing some crazy shit with COM apartment models, and to my disadvantage I was using them inside AspNet, I was literally deadlocking the Thread Pool, the solution was running the code single threaded in a new thread and manually synchronizing code by using an AsyncQueue of sorts, why did I ever try to use the Async methods... lesson learned. I though Contexts could solve it, but nope. Oh, there's COM apartment models bullshit. Now that I remembered this, DotNet mistake with CA(false) seems like a walk in the park.

Besides, any library you use provides you with a "contract" (the contract is often implicit though, so you have to discover it), if you don't uphold that contract and it means your application crashes, well then that is on you. So if a library applies ConfigureAwait(false) to all it's await and that somehow breaks your application, then that is not really the libraries problem, it's yours. But I am still VERY curious to see some examples of this in this particular situation. And even if there is some, I am sure they are easily fixed from the consumer's perspective.

I would find it amusing if that happened, it means the library contract is badly designed, it has literal undefined behavior and just happens to work. If you get to this point, all bets are lost, you can either live with it and don't change anything, or rewrite all uses of Task library. Changing a task library is a big task, but arguably doable, if your software has proper tests, shouldn't be too bad.

Now... If we talk about flipping the "default" behavior for all Tasks tomorrow, then obviously it would be a massive breaking change that will break applications all over the world. There is no doubt about that, that is why I think that introducing a [assembly:TaskConfigureAwait(false)] attribute that ONLY flips the default for awaits in codepath's inside that assembly is a reasonable way to go.

As I said, asking for breaking changes in working code is a obvious no go.

Where as solutions like ThreadPool.MakeDefaultConfigureAwait(false); is obviously a big NO GO as that would affect code that might be based on the old default. E.g. if you consume a Form Controls library that uses the TPL on some places.

This is TERRIBLE, this will do more harm than good, don't ever think about it. Better literally to not do anything than creating Global State that configures behavior of asynchronous code.

The important thing is that whatever is done to ease this, is something that only affects code "in your control".

My solution of replacing TPL would only affect precisely what you choose to affect.

Luiz-Monad commented 2 years ago

@jeme You could take this argument even further: Should there be a SynchronizationContext at all? Should there be any ambient context at all (ExecutionContext, TaskScheduler.Current, TransactionScope.Current, any kind of thread affinity, ...).

These things are super dangerous. They make code non-local, non-composable and the bugs are nasty.

But they are so convenient... I'm really undecided on this. I sometimes think that these contexts optimize for novice programmers and demo-ware in which the goal is to make things "just work". The more scale the codebase has the more it requires preciseness and modularity.

Creating a ConfigureAwait(false) default sure is possible. The attribute could use a new C# language feature that allows wrapping an await expression in an arbitrary other expression. The C# language would remain neutral and just provide the infrastructure.

The perils of implicit hidden mutable state. Async code should be Pure, but that's impossible to control with C#... Which is one of the reasons I'm migrating to F#.

If only there was better research on Dependent Types, you could tag the contexts in the type and it would be a compile time error to use some functions in some contexts, you could explicit code in the type the invariants you require. For example, this code needs to never run continuations in the same Thread because its a danger. Or this code needs to always run sequentially on the same Thread, even if at a later time, asynchronously.

You could do that with generics, but it becomes a pain very fast without Rank-N types or Dependent Types. You would start getting insane things like CustomThreadPoolWithFourThreads<Transacted<DatabaseExecutionContext<Task<Result>>>>, some which would need to be homomorphic and the same type as Transacted<CustomThreadPoolWithFourThreads<DatabaseExecutionContext<Task<Result>>>>. This would be a totally ridiculous way of writing code. It would be better to have something like:

async dependent Result doSomething (param) 
    where dependency is Transacted CustomThreadPoolWithFourThreads DatabaseContext Task {  //see that Task is also a typed dependency not a generalized type
/*code*/ 
}

then on calling, inside another async.

async dependent void onRun() when dependency is Task {
    var pool = ThreadPool(4);
    var result = await doSomething (SomeParam()) with TransactionScope(), pool, dbContext;
}
// or even if you are already inside some contexts
async dependent void onRun() when dependency is Transacted DatabaseContext {
   var pool = ThreadPool(4);
   var result = await doSomething (SomeParam()) with pool;
} // Task dependency context just flows, not even required because of async/await.

Actually you could even get rid of the async/await keyword, you know you are inside Task context.

But this is for future research... (if only I had a position in the Research Division, I would love to play with those things).

HaloFour commented 2 years ago

@Luiz-Monad

An interesting approach would be to use a different version of the TPL library that always do that.

If your projects never need synchronization then you'd be fine simply never setting a SynchronizationContext, then you don't need to care about CA(false). If you're working on ASP.NET Core then this is already the case.

You call CA(false) then you're likely to be running on a project that does (or may) require synchronization. Replacing the TPL library entirely so that there is no synchronization would completely break those projects and would never happen.

epeshk commented 1 year ago

IMHO, this may be implemented like nullable context, with ability to enable/disable implicit .ConfigureAwait(...) generation on assembly level via project property, or per code-region via directives:

*.csproj:

<ConfigureAwait>false</ConfigureAwait>

*.cs:

#configureawait true
public static async Task ContextDependentMethod() { ... }
#configureawait restore

UPD: Ok, introducing additional context is bad idea, class/method attribute for changing assembly-level default will be better.

MisinformedDNA commented 10 months ago

That's why i view this better as a tooling problem

Two years later, and I don't see any default tooling in VS. I have to add the VS Threading library.

And if tooling knows to just blindly add it everywhere, then the compiler should be able to do the same and reduce the noise.

jnm2 commented 10 months ago

It isn't designed to be blindly added everywhere, though.

ufcpp commented 10 months ago

dotnet_diagnostic.CA2007.severity = warning

austinw-fineart commented 10 months ago

With the death of WinForms and WPF it should be safe to deprecate ConfigureAwait

theunrepentantgeek commented 10 months ago

With the death of WinForms and WPF it should be safe to deprecate ConfigureAwait

Someone should tell the millions of developers using WinForms and WPF that this is happening. 😁

More seriously, those technologies are far from dead - there are a huge number of actively maintained projects and products that still deliver huge value.

MisinformedDNA commented 10 months ago

It isn't designed to be blindly added everywhere, though.

If it's a non-UI library, then we are adding it blindly everywhere. We haven't had any issues yet and if we did, it would be the exception and we would be happy to explicitly set it. Even @CyrusNajmabadi says he uses tooling to add it everywhere.

spottedmahn commented 7 months ago

this might interest some folks: ConfigureAwait in .NET 8 by Steve Cleary

reference: https://github.com/dotnet/runtime/pull/87067

epeshk commented 7 months ago

@spottedmahn nobody wants to write more ConfigureAwaits. SupressThrowing is quite useful to reduce exception handling overhead, though.

It would be nice to have an option (in csproj, or via assembly attribute) to generate assembly-level default ConfigureAwaits by Roslyn.

jeme commented 4 months ago

@spottedmahn as @epeshk wrote, we I think most people posting here don't want more of that stuff but rather less!

While SupressThrowing could be seen as a useful concept, since we already are calling a method I can't see why this couldn't be done as an extension method of sorts. (e.g. perhaps kind of like https://github.com/brminnick/AsyncAwaitBestPractices/tree/main/src/AsyncAwaitBestPractices)

And that probably also goes for the ForceYielding as well - So I have a hard time seeing that the work that went into that change will pay off.

In fact, it just brings some new awfulness to the API, e.g. the following is invalid and will give you a runtime exception: (it compiles just fine! - although you may raise a warning to error to avoid that)

int result = await Task.Run(() => {
    throw new NotImplementedException();
    return 42;
}).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);

So I am not sure, It feels more like a step in the wrong direction to me. But I don't know, maybe there is some benefits to having the API like this over a more explicit one as the first I linked.