Question

I have some code that does something like this:

abstract class Data
{
    Data(string name, bool load) { if (load) { Load().Wait(); }
    abstract Task Load();
}

class XmlData : Data
{
    XmlData(string name, bool load = true) : base(name, load) {}
    override async Task Load()
    {
        var file = await GetFileAsync(...);
        var xmlDoc = await LoadXmlDocAsync(file);
        ProcessXml(xmlDoc);
    }
    void ProcessXml(XmlDocument xmlDoc)
    {
        foreach (var element in xmlDoc.Nodes)
        {
            if (element.NodeName == "something")
                new XmlData(element.NodeText);
        }
    }
}

I seem to (sometimes) get wierd timing issues, where it ends up hanging the code at GetFileAsync(...). Is this caused by the recursive nature of the calls? When I change all the await calls to actually do a .Wait() for them to finish, and essentially get rid of all the asynchronous nature of the calls, my code executes fine.

Was it helpful?

Solution

Is this caused by the recursive nature of the calls? When I change all the await calls to actually do a .Wait() for them to finish, and essentially get rid of all the asynchronous nature of the calls, my code executes fine.

It really depends -

The most likely culprit would be if your caller is somehow blocking the user interface thread (via a call to Wait(), etc). In this case, the default behavior of await is to capture the calling synchronization context, and post the results back on that context.

However, if a caller is using that context, you can get a deadlock.

This is very likely the case, and being caused by this line of code:

Data(string name, bool load) { if (load) { Load.Wait(); }

This can be easily avoided by making your library code (like this XmlData class) explicitly not use the calling synchronization context. This is typically only required for user interface code. By avoiding the capture, you do two things. First, you improve teh overall performance (often dramatically), and second, you avoid this dead lock condition.

This can be done by using ConfigureAwait and changing your code like so:

override async Task Load()
{
    var file = await GetFileAsync.(...).ConfigureAwait(false);
    var xmlDoc = await LoadXmlDocAsync(file).ConfigureAwait(false);
    ProcessXml(xmlDoc);
}

That being said - I would rethink this design a bit. There are really two problems here.

First, your putting a virtual method call in your constructor, which is fairly dangerous, and should be avoided when possible, as it can lead to unusual problems.

Second, your turning your entire asynchronous operation into a synchronous operation by putting this in the constructor with the block. I would, instead, recommend rethinking this entire thing.

Perhaps you could rework this to make some form of factory, which returns your data loaded asynchronously? This could be as simple as making the public api for creation a factory method that returns Task<Data>, or even a generic public async Task<TData> Create<TData>(string name) where TData : Data method, which would allow you to keep the construction and loading asynchronous and avoid the blocking entirely.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top