4

Implementing a reusable adaptor type of library on top of Azure Document Db Client SDK.

The library can run anywhere, not only in an ASP.NET Core web service, but in a command line app, ASP.NET Web Api etc.

In this library all methods are async and they are simply layers of abstraction that makes it easier to use Document Db client api s. The only real async call - I/O request - is actually done on the lowest layer by the api s from the Document Db SDK. Any code above that I wrote is just in memory data transformations, conversions, no actual I/O call is involved but they are all async as well since the lowest layer Document Db api s are async.

Do I still need to use ConfigureAwait(false) on all layers of upper level code in the stack, or is that enough to only call ConfigureAwait(False) on the lowest layer of my own code that calls Document Db SDK s methods which make the real I/O call ?

Dogu Arslan
  • 3,292
  • 24
  • 43
  • 2
    Possible duplicate of [Best practice to call ConfigureAwait for all server-side code](https://stackoverflow.com/questions/13489065/best-practice-to-call-configureawait-for-all-server-side-code) – Pavel Anikhouski Jul 16 '19 at 10:05
  • Not sure that is a duplicate. That's about a web context, are azure document stores in a web context? – Liam Jul 16 '19 at 10:07
  • So the program is an Azure client, and runs on the desktop? Or is this something that runs on Azure? – John Wu Jul 16 '19 at 10:10
  • 1
    "ASP.NET Core team themselves have dropped the use of ConfigureAwait(false)". Source: https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html – Fabjan Jul 16 '19 at 10:10
  • Thanks I read that answer but I do not think my questions is answered there. I know in a library code it is reccommended to use ConfigureAwait(false) but my question is specific to the uppper layers of library code that does not make any I/O request and whether using ConfigureAwait(false) has any benefit there. – Dogu Arslan Jul 16 '19 at 10:11

2 Answers2

4

The continuations unlike a synchronous code which precedes an await call, are executed in context of different call-stack calls. In particular they are scheduled for execution as separate delegates and there is no call-stack relationship between them. So specifying ConfigureAwait(false) for very last continuation execution will take effect only for this specific continuation, other continuations will still execute using their individual scheduling configuration. That is, if your goal is to ensure not capturing a synchronization context by any continuation in your library (in order to prevent potential dead locks or any other reason) then you should configure all await calls which have continuations with ConfigureAwait(false).

Dmytro Mukalov
  • 1,949
  • 1
  • 9
  • 14
4

ConfigureAwait(false) is used to prevent execution on initial SynchronizationContext. If you're working on library which doesn't need access to UI thread for instance (in case of WPF or WinForms) you should use ConfigureAwait(false) on all levels. Otherwise SynchronizationContext will be restored. Here is an example of simple WinForms application:

public partial class Form1 : Form
{
    static readonly HttpClient _hcli = new HttpClient();

    public Form1()
    {
        InitializeComponent();
    }

    private static string log;
    private async void button1_Click(object sender, EventArgs e)
    {
        log = "";
        await M3();
        MessageBox.Show(log);
    }

    static async Task<string> M3()
    {
        LogBefore(nameof(M3));
        var str = await M2();
        LogAfter(nameof(M3));
        return str;
    }

    static async Task<string> M2()
    {
        LogBefore(nameof(M2));
        var str = await M1();
        LogAfter(nameof(M2));
        return str;
    }

    static async Task<string> M1()
    {
        LogBefore(nameof(M1));
        var str = await _hcli.GetStringAsync("http://mtkachenko.me").ConfigureAwait(false);
        LogAfter(nameof(M1));
        return str;
    }

    static void LogBefore(string method)
    {
        log += $"before {method} {Thread.CurrentThread.ManagedThreadId} {SynchronizationContext.Current == null}, ";
    }

    static void LogAfter(string method)
    {
        log += $"after {method} {Thread.CurrentThread.ManagedThreadId} {SynchronizationContext.Current == null}, ";
    }
}

Output:

before M3 1 False
before M2 1 False
before M1 1 False
after M1 12 True //sync.context skipped because of .ConfigureAwait(false)
after M2 1 False //sync.context restored
after M3 1 False //sync.context restored
mtkachenko
  • 5,389
  • 9
  • 38
  • 68