csharplang icon indicating copy to clipboard operation
csharplang copied to clipboard

[Proposal] Forcing ConfigureAwait(false) on assembly level

Open ashmind opened this issue 9 years ago • 129 comments

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.

ashmind avatar Dec 22 '15 21:12 ashmind

@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 avatar Dec 22 '15 22:12 alrz

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

ashmind avatar Dec 22 '15 22:12 ashmind

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 awaits.

paulomorgado avatar Dec 23 '15 00:12 paulomorgado

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 avatar Dec 23 '15 01:12 HaloFour

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

ashmind avatar Dec 23 '15 01:12 ashmind

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.

HaloFour avatar Dec 23 '15 02:12 HaloFour

+1 It's indeed a pain to have ConfigureAwait(false) everywhere in practice for all libraries.

vermorel avatar Jun 04 '16 15:06 vermorel

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.

NickCraver avatar Jan 09 '17 16:01 NickCraver

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.

petertiedemann avatar Feb 17 '17 10:02 petertiedemann

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 avatar Feb 17 '17 11:02 ufcpp

@ufcpp pretty nice solution.

Why they didnt added something like that to the TPL?

lanwin avatar Feb 18 '17 08:02 lanwin

I too find the current default maddening. What percentage does sync-requiring GUI code represent of all C# code being written today, anyway? Sigh.

asbjornu avatar May 19 '17 08:05 asbjornu

..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.

alrz avatar May 31 '17 15:05 alrz

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 avatar Feb 27 '18 12:02 AgentFire

@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 avatar Feb 27 '18 13:02 HaloFour

@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

AgentFire avatar Feb 27 '18 13:02 AgentFire

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

HaloFour avatar Feb 27 '18 13:02 HaloFour

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.

dominikjeske avatar Jul 04 '18 09:07 dominikjeske

Or maybe we can add a function-level attribute? For example:

[ConfigureAwait(false)]
public async Task FooBar() {...}

enihcam avatar Jul 11 '18 05:07 enihcam

It should be for method, class and assembly level ;)

dominikjeske avatar Jul 11 '18 08:07 dominikjeske

@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 avatar Jul 26 '18 06:07 wachulski

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

ashmind avatar Jul 26 '18 23:07 ashmind

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 avatar Jul 27 '18 00:07 sharwell

@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 avatar Jul 27 '18 05:07 petertiedemann

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

sharwell avatar Jul 27 '18 13:07 sharwell

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.

effyteva avatar Oct 05 '18 09:10 effyteva

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 avatar Oct 05 '18 20:10 CyrusNajmabadi

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

tmat avatar Oct 05 '18 21:10 tmat

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.

CyrusNajmabadi avatar Oct 05 '18 21:10 CyrusNajmabadi

Generators would address that.

tmat avatar Oct 05 '18 21:10 tmat