Могут ли конструкторы быть асинхронными? - PullRequest
231 голосов
/ 16 ноября 2011

У меня есть проект, в котором я пытаюсь заполнить некоторые данные в конструкторе:

public class ViewModel
{
    public ObservableCollection<TData> Data { get; set; }

    async public ViewModel()
    {
        Data = await GetDataTask();
    }

    public Task<ObservableCollection<TData>> GetDataTask()
    {
        Task<ObservableCollection<TData>> task;

        //Create a task which represents getting the data
        return task;
    }
}

К сожалению, я получаю сообщение об ошибке:

Модификатор async недействителен для этого элемента

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

public async void Foo()
{
    Data = await GetDataTask();
}

работает нормально. Точно так же, если я использую старый способ наизнанку

GetData().ContinueWith(t => Data = t.Result);

Это тоже работает. Мне просто интересно, почему мы не можем вызвать await из конструктора напрямую. Вероятно, есть много (даже очевидных) крайних случаев и причин против этого, я просто не могу думать ни о каком. Я также ищу объяснения, но не могу найти ни одного.

Ответы [ 13 ]

190 голосов
/ 21 сентября 2012

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

   public class ViewModel       
   {       
    public ObservableCollection<TData> Data { get; set; }       

    //static async method that behave like a constructor       
    async public static Task<ViewModel> BuildViewModelAsync()  
    {       
     ObservableCollection<TData> tmpData = await GetDataTask();  
     return new ViewModel(tmpData);
    }       

    // private constructor called by the async method
    private ViewModel(ObservableCollection<TData> Data)
    {
     this.Data=Data;   
    }
   }  
178 голосов
/ 16 ноября 2011

Конструктор действует очень похоже на метод, возвращающий составной тип.И метод async не может возвращать просто любой тип, он должен быть либо «запусти и забудь» void, либо Task.

Если конструктор типа T действительно вернул Task<T>, это было бы очень запутанным, я думаю.

Если асинхронный конструктор вел себя так же, как и метод async void, такой тип нарушает то, что подразумевается под конструктором.После возврата конструктора вы должны получить полностью инициализированный объект.Не объект, который будет фактически должным образом инициализирован в некоторой неопределенной точке в будущем.То есть, если вам повезет и асинхронная инициализация не завершится неудачей.

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

Если вы на самом деле хотите семантику «запустить и забыть» async void методов (которых следует избегать, если это возможно)), вы можете легко инкапсулировать весь код в методе async void и вызывать его из своего конструктора, как вы упомянули в вопросе.

40 голосов
/ 17 июля 2015

Ваша проблема сопоставима с созданием файлового объекта и открытием файла.На самом деле существует множество классов, в которых вам нужно выполнить два шага, прежде чем вы сможете использовать объект: create + Initialize (часто называемый чем-то похожим на Open).

Преимущество этого состоит в том, что конструктор можетбыть легкимПри желании вы можете изменить некоторые свойства перед фактической инициализацией объекта.Когда все свойства установлены, вызывается функция Initialize / Open для подготовки объекта к использованию.Эта Initialize функция может быть асинхронной.

Недостатком является то, что вы должны доверять пользователю вашего класса, который он вызовет Initialize(), прежде чем он использует любую другую функцию вашего класса.На самом деле, если вы хотите сделать ваш класс полным доказательством (дурака?), Вы должны проверять каждую функцию, что был вызван Initialize().

Шаблон, облегчающий эту задачу, заключается в объявлении конструктора частными создайте общедоступную статическую функцию, которая будет создавать объект и вызывать Initialize() перед возвратом созданного объекта.Таким образом, вы будете знать, что каждый, кто имеет доступ к объекту, использовал функцию * 1013. *

В этом примере показан класс, имитирующий требуемый асинхронный конструктор

public MyClass
{
    public static async Task<MyClass> CreateAsync(...)
    {
        MyClass x = new MyClass();
        await x.InitializeAsync(...)
        return x;
    }

    // make sure no one but the Create function can call the constructor:
    private MyClass(){}

    private async Task InitializeAsync(...)
    {
        // do the async things you wanted to do in your async constructor
    }

    public async Task<int> OtherFunctionAsync(int a, int b)
    {
        return await OtherFunctionAsync(a, b);
    }

Использование будетбыть следующим:

public async Task<int> SomethingAsync()
{
    // Create and initialize a MyClass object
    MyClass myObject = await MyClass.CreateAsync(...);

    // use the created object:
    return await myObject.OtherFunctionAsync(4, 7);
}
4 голосов
/ 31 декабря 2014

В этом конкретном случае viewModel требуется для запуска задачи и уведомления представления о ее завершении. «Асинхронное свойство», а не «асинхронный конструктор», находится в порядке.

Я только что выпустил AsyncMVVM , которая решает именно эту проблему (среди прочих). Если вы используете его, ваша ViewModel станет:

public class ViewModel : AsyncBindableBase
{
    public ObservableCollection<TData> Data
    {
        get { return Property.Get(GetDataAsync); }
    }

    private Task<ObservableCollection<TData>> GetDataAsync()
    {
        //Get the data asynchronously
    }
}

Как ни странно, Silverlight поддерживается. :)

2 голосов
/ 03 мая 2017

Мне было просто интересно, почему мы не можем вызвать await напрямую из конструктора.

Я считаю, что короткий ответ прост: потому что команда .Net не запрограммировалаэта функция.

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

2 голосов
/ 16 ноября 2011

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

MyClass instance = new MyClass();
instance.Foo(); // null exception here

Вот почему они не допускают этого, я думаю.

1 голос
/ 08 ноября 2012

вызов асинхронного в конструкторе может вызвать тупик, пожалуйста, обратитесь к http://social.msdn.microsoft.com/Forums/en/winappswithcsharp/thread/0d24419e-36ad-4157-abb5-3d9e6c5dacf1

http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx

0 голосов
/ 27 февраля 2019

Некоторые ответы включают создание нового метода public.Без этого используйте класс Lazy<T>:

public class ViewModel
{
    private Lazy<ObservableCollection<TData>> Data;

    async public ViewModel()
    {
        Data = new Lazy<ObservableCollection<TData>>(GetDataTask);
    }

    public ObservableCollection<TData> GetDataTask()
    {
        Task<ObservableCollection<TData>> task;

        //Create a task which represents getting the data
        return task.GetAwaiter().GetResult();
    }
}

Для использования Data используйте Data.Value.

0 голосов
/ 05 февраля 2019

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

0 голосов
/ 17 апреля 2017

Я бы использовал что-то вроде этого.

 public class MyViewModel
    {
            public MyDataTable Data { get; set; }
            public MyViewModel()
               {
                   loadData(() => GetData());
               }
               private async void loadData(Func<DataTable> load)
               {
                  try
                  {
                      MyDataTable = await Task.Run(load);
                  }
                  catch (Exception ex)
                  {
                       //log
                  }
               }
               private DataTable GetData()
               {
                    DataTable data;
                    // get data and return
                    return data;
               }
    }

Это как можно ближе к конструкторам.

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