Могут ли конструкторы быть асинхронными?

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

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 непосредственно из конструктора. Вероятно, есть много (даже очевидных) крайних случаев и причин против него, я просто не могу придумать ни одного. Я также ищу объяснение, но, похоже, не могу его найти.

11 ответов


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

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

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

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

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


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

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

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

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


в этом конкретном случае 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 поддерживается. :)


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

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

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


вызов асинхронности в конструкторе может привести к взаимоблокировке, см. 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


мне просто интересно, почему мы не можем назвать await непосредственно из конструктора.

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

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


Я не знаком с ключевым словом async (это относится к Silverlight или к новой функции в бета-версии Visual Studio?), но я думаю, что могу дать вам представление о том, почему вы не можете этого сделать.

Если я это сделаю:

var o = new MyObject();
MessageBox(o.SomeProperty.ToString());

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


вы можете использовать действие внутри конструктора

 public class ViewModel
    {
        public ObservableCollection<TData> Data { get; set; }
       public ViewModel()
        {              
            new Action(async () =>
            {
                  Data = await GetDataTask();
            }).Invoke();
        }

        public Task<ObservableCollection<TData>> GetDataTask()
        {
            Task<ObservableCollection<TData>> task;
            //Create a task which represents getting the data
            return task;
        }
    }

Я использую этот простой трюк.

public sealed partial class NamePage
{
  private readonly Task _initializingTask;

  public NamePage()
  {
    _initializingTask = Init();
  }

  private async Task Init()
  {
    /*
    Initialization that you need with await/async stuff allowed
    */
  }
}

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

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

Это я могу сделать для конструкторов.