csharplang
csharplang copied to clipboard
[Proposal] Forcing ConfigureAwait(false) on assembly level
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
- Implement CallerMethodAttribute from #351
- Add support for method info attributes to pattern-based GetAwaiter calls
- This would allow for a new overload
GetAwaiter([CallerMethod] MethodBase method = null)
-
Task
could use this overload to look for some attribute onmethod.Assembly
and return a correspondingly preconfigured awaiter.
@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.
@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.
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.
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.
@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?
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.
+1 It's indeed a pain to have ConfigureAwait(false)
everywhere in practice for all libraries.
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.
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.
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.
@ufcpp pretty nice solution.
Why they didnt added something like that to the TPL?
I too find the current default maddening. What percentage does sync-requiring GUI code represent of all C# code being written today, anyway? Sigh.
..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.
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.
@AgentFire
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.
@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.
@AgentFire
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.
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.
Or maybe we can add a function-level attribute? For example:
[ConfigureAwait(false)]
public async Task FooBar() {...}
It should be for method, class and assembly level ;)
@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.
@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.
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.
@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)?
@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.
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.
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?
@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.
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.
Generators would address that.