I'm writing a method which asynchronously writes separate lines of text to a file. If it's cancelled it deletes the created file and jumps out of the loop.
This is the simplified code which works fine... And I marked 2 points which I'm not sure how they are being handled. I want the code to not block the thread in any case.
public async Task<IErrorResult> WriteToFileAsync(string filePath,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using var stream = new FileStream(filePath, FileMode.Create);
using var writer = new StreamWriter(stream, Encoding.UTF8);
foreach (var line in Lines)
{
if (cancellationToken.IsCancellationRequested)
{
//
// [1] close, delete and throw if cancelled
//
writer.Close();
stream.Close();
if (File.Exists(filePath))
File.Delete(filePath);
throw new OperationCanceledException();
}
// write to the stream
await writer.WriteLineAsync(line.ToString());
}
//
// [2] flush and let them dispose
//
await writer.FlushAsync();
await stream.FlushAsync();
// await stream.DisposeAsync(); ??????
return null;
}
I'm calling Close()
on FileStream
and StreamWriter
and I think it will run synchronously and blocks the thread. How can I improve this? I don't want to wait for it to flush the buffer into the file and then delete the file.
I suppose the Dispose
method will be called and not DisposeAsync
at the end of the using
scope. (is this assumption correct?).
So Dispose
blocks the thread and in order to prevent that I'm flushing first with FlushAsync
so that Dispose
would perform less things. (to what extent is this true?)
I could also remove using
and instead I could write DisposeAsync
manually in these two places. But it will decrease readability.
If I open the FileStream
with useAsync = true
would it automatically call DisposeAsync
when using
block ends?
Any explanation or a variation of the above code which performs better is appreciated.
DisposeAsync() method when you need to perform resource cleanup, just as you would when implementing a Dispose method. One of the key differences, however, is that this implementation allows for asynchronous cleanup operations. The DisposeAsync() returns a ValueTask that represents the asynchronous disposal operation.
When the close brace is reached, the Dispose( ) method will be called on the object automatically, as illustrated in Example 4-6. In the first part of this example, the Font object is created within the using statement. When the using statement ends, Dispose( ) is called on the Font object.
As you have it, the using
statement will call Dispose()
, not DisposeAsync()
.
C# 8 brought a new await using
syntax, but for some reason it's not mentioned in the What's new in C# 8.0 article.
But it's mentioned elsewhere.
await using var stream = new FileStream(filePath, FileMode.Create);
await using var writer = new StreamWriter(stream, Encoding.UTF8);
But also note that this will only work if:
IAsyncDisposable
was introduced, orStream
types (FileStream
, StreamWriter
, etc.) that use it.Even in the Announcing .NET Core 3.0 article, IAsyncDisposable
is only mentioned in passing and never expanded on.
On another note, you don't need to do this (I see why now):
writer.Close();
stream.Close();
Since the documentation for Close
says:
This method calls Dispose, specifying
true
to release all resources. You do not have to specifically call the Close method. Instead, ensure that every Stream object is properly disposed.
Since you're using using
, Dispose()
(or DisposeAsync()
) will be called automatically and Close
won't do anything that's not already happening.
So if you do need to specifically close the file, but want to do it asynchronously, just call DisposeAsync()
instead. It does the same thing.
await writer.DisposeAsync();
public async Task<IErrorResult> WriteToFileAsync(string filePath,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await using var stream = new FileStream(filePath, FileMode.Create);
await using var writer = new StreamWriter(stream, Encoding.UTF8);
foreach (var line in Lines)
{
if (cancellationToken.IsCancellationRequested)
{
// not possible to discard, FlushAsync is covered in DisposeAsync
await writer.DisposeAsync(); // use DisposeAsync instead of Close to not block
if (File.Exists(filePath))
File.Delete(filePath);
throw new OperationCanceledException();
}
// write to the stream
await writer.WriteLineAsync(line.ToString());
}
// FlushAsync is covered in DisposeAsync
return null;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With