Youngjae
Youngjae

Reputation: 25050

XElement to file save with async/await in C#

I tried to write xml file from XElement object with async/await feature. But I realized the XElement.Save() does not operate with async/await.

Maybe the solution can be to use XElement.Save(Stream) with FileStream object...

So, I write some code as below but hard to treat with filestream things.

public async Task SaveAsync(XElement xml, string filename)
{
    using (var fs = new FileStream(filename, FileMode.Create))
    {
        xml.Save(fs);
        await fs.WriteAsync(**please_help_me**);
    }
}

how to do with this approach or is there any other solution?

Upvotes: 2

Views: 5213

Answers (3)

dbc
dbc

Reputation: 116825

As of .NET Core 2.0 and .NET Standard 2.1, XElement and XDocument both have SaveAsync() methods. Thus you can now do:

public static async Task SaveAsync(this XElement xml, string filename, SaveOptions options = default, CancellationToken cancellationToken = default)
{
    // "await using" introduced in c# 8 / .NET Core 3.0+
    await using var stream =
        new FileStream(
        filename, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, 
        useAsync: true);

    await xml.SaveAsync(stream, options, cancellationToken);
}

Notes:

  • In .NET Core 3, XDocument.SaveAsync() (and probably XElement.SaveAsync() also) still contained at least one blocking call. See XDocument.SaveAsync has a blocking call #29464. The issue should be fixed in .NET 5.

  • According to the docs, the useAsync argument to the FileStream constructor

    Specifies whether to use asynchronous I/O or synchronous I/O. However, note that the underlying operating system might not support asynchronous I/O, so when specifying true, the handle might be opened synchronously depending on the platform. When opened asynchronously, the BeginRead(Byte[], Int32, Int32, AsyncCallback, Object) and BeginWrite(Byte[], Int32, Int32, AsyncCallback, Object) methods perform better on large reads or writes, but they might be much slower for small reads or writes. If the application is designed to take advantage of asynchronous I/O, set the useAsync parameter to true. Using asynchronous I/O correctly can speed up applications by as much as a factor of 10, but using it without redesigning the application for asynchronous I/O can decrease performance by as much as a factor of 10.

    Since you know you want to write your XElement asynchronously, presumably you have designed your application for asynchronous I/O. However, you might want to benchmark with and without asynchronous IO to confirm you are not harming performance.

Demo fiddle here.

Upvotes: 3

spender
spender

Reputation: 120498

If you really don't want to block on IO (and hang a ThreadPool thread/your thread), you'll probably need to use a temporary MemoryStream:

using(var ms = new MemoryStream())
using(var fs = new FileStream("myFile", FileMode.Create))
{
    xml.Save(ms);
    ms.Position = 0;
    await ms.CopyToAsync(fs)
}

Upvotes: 6

carlosfigueira
carlosfigueira

Reputation: 87238

XElement does not have any methods to write itself asynchronously, so any calls you make with it will be synchronous. If you need to make this method asynchronous (for example, this happens in the UI thread, and you want to do it on the background thread so that the application doesn't appear to "freeze" while the XML is being saved), you may want to start a new task, and save it on the background.

public async Task SaveAsync(XElement xml, string filename)
{
    await Task.Factory.StartNew(delegate
    {
        using (var fs = new FileStream("myFile", FileMode.Create))
        {
            xml.Save(fs);
        }
    });
}

Upvotes: 2

Related Questions