Вызовите асинхронный метод в конструкторе?

Резюме. Я хотел бы вызвать асинхронный метод в конструкторе. Возможно ли это?

Детали. У меня есть метод под названием getwritings(), который анализирует данные JSON. Все работает нормально, если я просто вызываю getwritings() в методе async и помещаю await влево от него. Однако, когда я создаю LongListView на моей странице и пытаюсь его заполнить, я обнаружил, что getwritings() неожиданно возвращается null, а LongListView пуст.

Чтобы решить эту проблему, я попытался изменить тип возврата getwritings() на Task<List<Writing>>, а затем получить результат в конструкторе через getWritings().Result. Тем не менее, это заканчивается блокировкой потока пользовательского интерфейса.

public partial class Page2 : PhoneApplicationPage
{
    List<Writing> writings;

    public Page2()
    {
        InitializeComponent();
        getWritings();
    }

    private async void getWritings()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");

            writings.Add(writing);
        }

        myLongList.ItemsSource = writings;
    }
}

Ответ 1

Лучшим решением является признание асинхронного характера загрузки и дизайна для него.

Другими словами, решите, как должно выглядеть ваше приложение во время загрузки данных. Создайте этот конструктор страницы и запустите загрузку. Когда загрузка завершается, обновите страницу, чтобы отобразить данные.

У меня есть сообщение в блоге асинхронных конструкторов, которое может показаться вам полезным. Кроме того, некоторые статьи MSDN; один в асинхронной привязке данных (если вы используете MVVM), а другой - в асинхронных лучших практиках (т.е., вы должны избегать async void).

Ответ 2

Вы также можете сделать следующее:

Task.Run(() => this.FunctionAsync()).Wait();

Ответ 3

Я хотел бы поделиться шаблоном, который я использовал для решения этих проблем. Я думаю, это работает неплохо. Конечно, он работает только в том случае, если у вас есть контроль над тем, что вызывает конструктор. Пример ниже

public class MyClass
{
    public static async Task<MyClass> Create()
    {
        var myClass = new MyClass();
        await myClass.Initialize();
        return myClass;
    }

    private MyClass()
    {

    }

    private async Task Initialize()
    {
        await Task.Delay(1000); // Do whatever asynchronous work you need to do
    }
}

В основном мы делаем конструктор private и создаем собственный публичный статический асинхронный метод, который отвечает за создание экземпляра MyClass. Сделав конструктор частным и сохраняя статический метод в одном классе, мы убедились, что никто не может "случайно" создать экземпляр этого класса, не вызывая правильные методы инициализации. Вся логика вокруг создания объекта все еще содержится внутри класса (только внутри статического метода).

var myClass1 = new MyClass() // Cannot be done, the constructor is private
var myClass2 = MyClass.Create() // Returns a Task that promises an instance of MyClass once it finished
var myClass3 = await MyClass.Create() // asynchronously creates and initializes an instance of MyClass

Реализованный по текущему сценарию будет выглядеть примерно так:

public partial class Page2 : PhoneApplicationPage
{
    public static async Task<Page2> Create()
    {
        var page = new Page2();
        await page.getWritings();
        return page;
    }

    List<Writing> writings;

    private Page2()
    {
        InitializeComponent();
    }

    private async Task getWritings()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");

            writings.Add(writing);
        }

        myLongList.ItemsSource = writings;
    }
}

И вместо того, чтобы делать

var page = new Page2();

Вы будете делать

var page = await Page2.Create();

Ответ 4

Попробуйте заменить это:

myLongList.ItemsSource = writings;

с этим

Dispatcher.BeginInvoke(() => myLongList.ItemsSource = writings);

Ответ 5

Проще говоря, ссылаясь на Стивена Клири fooobar.com/questions/41188/...

ваша страница при создании должна создавать задачи в конструкторе, и вы должны объявлять эти задачи как члены класса или помещать их в пул задач.

Ваши данные извлекаются во время этих задач, но эти задачи должны ожидаться в коде, то есть в некоторых манипуляциях с пользовательским интерфейсом, то есть в режиме "ОК" и т.д.

Я разработал такие приложения в WP, у нас было целая куча задач, созданных при запуске.

Ответ 6

Вы можете попробовать AsyncMVVM.

Page2.xaml:

<PhoneApplicationPage x:Class="Page2"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <ListView ItemsSource="{Binding Writings}" />
</PhoneApplicationPage>

Page2.xaml.cs:

public partial class Page2
{
    InitializeComponent();
    DataContext = new ViewModel2();
}

ViewModel2.cs:

public class ViewModel2: AsyncBindableBase
{
    public IEnumerable<Writing> Writings
    {
        get { return Property.Get(GetWritingsAsync); }
    }

    private async Task<IEnumerable<Writing>> GetWritingsAsync()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");
            yield return writing;
        }
    }
}

Ответ 7

Быстрый способ выполнить некоторую трудоемкую операцию в любом конструкторе - создать действие и запустить их асинхронно.

new Action( async() => await GetDataAsync())();

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

Примечание. Этот параметр обеспечивает способ запуска метода только из конструктора, если у вас нет переопределений init, onload или navigated. Скорее всего, он будет продолжать работать даже после завершения строительства. Следовательно, результат этого вызова метода не будет доступен в самом конструкторе.

Ответ 8

Немного опоздал на вечеринку, но я думаю, что многие борются с этим...

Я тоже искал это. И чтобы ваш метод/действие выполнялось асинхронно без ожидания или блокировки потока, вам нужно поставить его в очередь через SynchronizationContext, поэтому я пришел к такому решению:

Я сделал для этого вспомогательный класс.

public static class ASyncHelper
{

    public static void RunAsync(Func<Task> func)
    {
        var context = SynchronizationContext.Current;

        // you don't want to run it on a threadpool. So if it is null, 
        // you're not on a UI thread.
        if (context == null)
            throw new NotSupportedException(
                "The current thread doesn't have a SynchronizationContext");

        // post an Action as async and await the function in it.
        context.Post(new SendOrPostCallback(async state => await func()), null);
    }

    public static void RunAsync<T>(Func<T, Task> func, T argument)
    {
        var context = SynchronizationContext.Current;

        // you don't want to run it on a threadpool. So if it is null, 
        // you're not on a UI thread.
        if (context == null)
            throw new NotSupportedException(
                "The current thread doesn't have a SynchronizationContext");

        // post an Action as async and await the function in it.
        context.Post(new SendOrPostCallback(async state => await func((T)state)), argument);
    }
}

Использование/Пример:

public partial class Form1 : Form
{

    private async Task Initialize()
    {
        // replace code here...
        await Task.Delay(1000);
    }

    private async Task Run(string myString)
    {

        // replace code here...
        await Task.Delay(1000);
    }

    public Form1()
    {
        InitializeComponent();

        // you don't have to await nothing.. (the thread must be running)
        ASyncHelper.RunAsync(Initialize);
        ASyncHelper.RunAsync(Run, "test");

        // In your case
        ASyncHelper.RunAsync(getWritings);
    }
}

Это работает для Windows. Forms и WPF.

Ответ 9

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

Main()  
{
  AnyMethodWithVoidReturnAsync().Wait();
  AnyMethodWithReturnTypeAsync().Result;
}