Рекурсивные проблемы асинхронной WinRT - PullRequest
2 голосов
/ 06 марта 2012

У меня есть код, который делает что-то вроде этого:

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);
        }
    }
}

Кажется, что (иногда) возникают странные проблемы с синхронизацией, когда в конечном итоге код зависает в GetFileAsync (...). Это вызвано рекурсивным характером вызовов? Когда я изменяю все вызовы await на фактическое выполнение .Wait () для их завершения и, по существу, избавляюсь от асинхронного характера вызовов, мой код выполняется нормально.

1 Ответ

3 голосов
/ 06 марта 2012

Это вызвано рекурсивной природой вызовов? Когда я изменяю все вызовы await на фактическое выполнение .Wait () для их завершения и, по существу, избавляюсь от асинхронного характера вызовов, мой код выполняется нормально.

Это действительно зависит -

Наиболее вероятный виновник был бы, если бы ваш абонент каким-то образом блокировал поток пользовательского интерфейса (через вызов Wait () и т. Д.). В этом случае стандартное поведение await заключается в захвате вызывающего контекста синхронизации и последующей публикации результатов в этом контексте.

Однако, если вызывающий объект использует этот контекст, вы можете получить тупик.

Это, скорее всего, тот случай, и он вызван этой строкой кода:

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

Этого можно легко избежать, явно указав код вашей библиотеки (например, этот класс XmlData) , а не , используя контекст синхронизации вызова. Обычно это требуется только для кода интерфейса пользователя. Избегая захвата, вы делаете две вещи. Во-первых, вы улучшаете общую производительность (часто радикально), а во-вторых, избегаете этого состояния мертвой блокировки.

Это можно сделать с помощью ConfigureAwait и изменив код следующим образом:

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

Как говорится, я бы немного переосмыслил этот дизайн. Здесь действительно две проблемы.

Во-первых, вы помещаете вызов виртуального метода в свой конструктор, что довольно опасно, и его следует избегать, когда это возможно, поскольку это может привести к необычным проблемам.

Во-вторых, вы превращаете всю свою асинхронную операцию в синхронную операцию, помещая ее в конструктор вместе с блоком. Вместо этого я бы порекомендовал переосмыслить всю эту вещь.

Возможно, вы могли бы переработать это, чтобы сделать какую-то фабрику, которая возвращает ваши данные, загруженные асинхронно? Это может быть так же просто, как сделать общедоступный API-интерфейс для создания фабричного метода, который возвращает Task<Data>, или даже универсальный метод public async Task<TData> Create<TData>(string name) where TData : Data, который позволит вам сохранить конструкцию и загрузку асинхронными и полностью избежать блокировки.

...