10

I have the following synchronous code, which works fine:

    private void GenerateExportOutput()
    {
        using StreamWriter writer = new(Coordinator.OutputDirectory + @"\export.txt");

        if (this.WikiPagesToExport.IsEmpty)
        {
            return;
        }

        var wanted = new SortedDictionary<string, WikiPage>(this.WikiPagesToExport, StringComparer.Ordinal);
        foreach (var title in wanted.Keys)
        {
            writer.WriteLine(title);
        }
    }

I want to change it to be asynchronous. So:

    private async Task GenerateExportOutputAsync()
    {
        using StreamWriter writer = new(Coordinator.OutputDirectory + @"\export.txt");

        if (this.WikiPagesToExport.IsEmpty)
        {
            return;
        }

        var wanted = new SortedDictionary<string, WikiPage>(this.WikiPagesToExport, StringComparer.Ordinal);
        foreach (var title in wanted.Keys)
        {
            await writer.WriteLineAsync(title).ConfigureAwait(false);
        }

        await writer.FlushAsync().ConfigureAwait(false);
    }

Which compiles. But one of the analyzers I use (Meziantou.Analyzer) now suggests that I "prefer using 'await using'". I've never used await using (though I've tried several times in the past and have always run into the same problems I'm running into now). But I would like to use it, so:

        await using StreamWriter writer = new StreamWriter(OutputDirectory + @"\export.txt").ConfigureAwait(false);

Now it no longer compiles: CS0029 Cannot implicitly convert type 'System.Runtime.CompilerServices.ConfiguredAsyncDisposable' to 'System.IO.StreamWriter'. OK, fine, so I change it to use var instead:

        await using var writer = new StreamWriter(OutputDirectory + @"\export.txt").ConfigureAwait(false);

Which gets it past the CS0029, but now the later code doesn't compile: Error CS1061 'ConfiguredAsyncDisposable' does not contain a definition for 'WriteLineAsync' (and a similar one for FlushAsync. Soooo... maybe cast it?

            await ((StreamWriter)writer).WriteLineAsync(title).ConfigureAwait(false);

Nope: Error CS0030 Cannot convert type 'System.Runtime.CompilerServices.ConfiguredAsyncDisposable' to 'System.IO.StreamWriter'

I've googled a bunch and read a bunch, both now and several times in the past, but I just have been completely unable to figure out how to use this "await using" thing. How can I do so? Thanks.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Bob Vesterman
  • 1,127
  • 1
  • 11
  • 31
  • 2
    https://www.tabsoverspaces.com/233779-using-await-using-iasyncdisposable-with-configureawait – madreflection Jan 27 '22 at 22:43
  • @madreflection, thank you! – Bob Vesterman Jan 27 '22 at 22:50
  • 2
    One more for good measure... https://github.com/dotnet/csharplang/discussions/2661 – madreflection Jan 27 '22 at 22:51
  • `using var writer = new StreamWriter("...");` then on the next line: `await writer.WriteLineAsync().ConfigureAwait(false);` – Charles Jan 27 '22 at 22:58
  • Side note: `ConfigureAwait(false)` only really needs to be used if you're developing a third-party library. If this project is a web or desktop application, you don't need it. Some people recommend you always use it, but that causes more trouble than any good (if any). I never use it. – Gabriel Luci Jan 27 '22 at 22:59
  • 1
    @Charles: That might work (as in not thrown an exception), but it won't call `IAsyncDisposable.Dispose` on the stream object. It'll use `IDisposable.Dispose` instead. – madreflection Jan 27 '22 at 23:00
  • @madreflection so first line `await using var writer = new StreamWriter("...") ;` – Charles Jan 27 '22 at 23:05
  • 1
    @Charles: Sure, except you can't use `ConfigureAwait(false)` on that directly, which is the crux of this question. The pages I linked show `await using (_variable_.ConfigureAwait(false))` *after* assigning something to `_variable_`. The variable has the right type, while `await using` separately captures the configured awaitable used for async disposal. – madreflection Jan 27 '22 at 23:05

2 Answers2

15

The await using syntax currently (C# 10) leaves a lot to be desired, regarding its support for configuring the awaiting of IAsyncDisposables. The best we can do is this:

private async Task GenerateExportOutputAsync()
{
    StreamWriter writer = new(Coordinator.OutputDirectory + @"\export.txt");
    await using (writer.ConfigureAwait(false))
    {
        //...
    }
}

...which is not really much more compact than not using the await using syntax at all:

private async Task GenerateExportOutputAsync()
{
    StreamWriter writer = new(Coordinator.OutputDirectory + @"\export.txt");
    try
    {
        //...
    }
    finally { await writer.DisposeAsync().ConfigureAwait(false); }
}

Related GitHub issue: Using ConfigureAwait in "await using" declaration.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
1

It can be done in two lines:

private async Task GenerateExportOutputAsync()
{
    StreamWriter writer = new(Coordinator.OutputDirectory + @"\export.txt");
    await using var _ = writer.ConfigureAwait(false);
    //...
}
logicnet.dk
  • 193
  • 1
  • 7
  • 1
    This creates a variable with the name `_`. It's not a [discard](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards). If you try to nest another `await using var _ =` in the same method, you'll get a compile error because of the doubly declared variable `_`. – Theodor Zoulias Aug 23 '22 at 20:30