ChatGPT解决这个技术问题 Extra ChatGPT

Can constructors be async?

I have a project where I'm trying to populate some data in a constructor:

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

Unfortunately, I'm getting an error:

The modifier async is not valid for this item

Of course, if I wrap in a standard method and call that from the constructor:

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

it works fine. Likewise, if I use the old inside-out way

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

That works too. I was just wondering why we can't call await from within a constructor directly. There are probably lots of (even obvious) edge cases and reasons against it, I just can't think of any. I've also search around for an explanation, but can't seem to find any.

No, but in his blog, Stephen Cleary offers a factory method approach as well as others to consider.
The pattern proposed in this answer works pretty well, it's an offshoot of the factory pattern, but I'm going to start referring to it, specifically, as the async constructor pattern.
Please bump this language request: github.com/dotnet/csharplang/discussions/419 - the amount of boilerplate code everyone needs to write to have a fully initialized async object is crazy and completely opposite of the trend in C# (less boilerplate).

U
Uwe Keim

Since it is not possible to make an async constructor, I use a static async method that returns a class instance created by a private constructor. This is not elegant but it works ok.

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

This answer should have much more votes in my opinion. It gives an answer that encapsulates and hides away the need to call an Initialize() method after an item is constructed, thus preventing the potential bug of constructing an object and forgetting to cal its initialize method.
Ag, this would be a great solution if you had control over the constructor but if your class implements an abstract base class, e.g. public class LoginModelValidator : AbstractValidator you have a problem
This approach uses a factory pattern. See another well-written similar answer here.
You don't always have control over callers, so a factory isn't always the general solution (to restate what Damian said in a more general way)
This is a nice solution from the "user" perspective, but it's quite common in i.e. web applications and you need a lot of boilerplate. It would be great if they would syntactic sugar this behavior in something similar like an async constructor.
s
svick

Constructor acts very similarly to a method returning the constructed type. And async method can't return just any type, it has to be either “fire and forget” void, or Task.

If the constructor of type T actually returned Task<T>, that would be very confusing, I think.

If the async constructor behaved the same way as an async void method, that kind of breaks what constructor is meant to be. After constructor returns, you should get a fully initialized object. Not an object that will be actually properly initialized at some undefined point in the future. That is, if you're lucky and the async initialization doesn't fail.

All this is just a guess. But it seems to me that having the possibility of an async constructor brings more trouble than it's worth.

If you actually want the “fire and forget” semantics of async void methods (which should be avoided, if possible), you can easily encapsulate all the code in an async void method and call that from your constructor, as you mentioned in the question.


I think this hits it closest. await can so often replace .ContinueWith that it was easy for me to forget that it's not so simple. I'm not even sure what I was thinking anymore, but I think I was thinking that await ought to "return" a constructed T (which you point out isn't what an async method can return) because that's was constructors "return" but then when await continues, the contructor doesn't return anything because its a constructor, like void. I'm not even making sense anymore, but your answer was the most helpful. Thanks.
"If the constructor of type T actually returned Task, that would be very confusing, I think." I disagree. Like async Dispose, it would be very natural.
"async void" Do not do that. The construction of the object is not complete. It can raise the exception which would not be handled and etc.
H
Harald Coppoolse

Your problem is comparable to the creation of a file object and opening the file. In fact there are a lot of classes where you have to perform two steps before you can actually use the object: create + Initialize (often called something similar to Open).

The advantage of this is that the constructor can be lightweight. If desired, you can change some properties before actually initializing the object. When all properties are set, the Initialize/Open function is called to prepare the object to be used. This Initialize function can be async.

The disadvantage is that you have to trust the user of your class that he will call Initialize() before he uses any other function of your class. In fact if you want to make your class full proof (fool proof?) you have to check in every function that the Initialize() has been called.

The pattern to make this easier is to declare the constructor private and make a public static function that will construct the object and call Initialize() before returning the constructed object. This way you'll know that everyone who has access to the object has used the Initialize function.

The example shows a class that mimics your desired async constructor

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 ... // return something useful
    }

Usage will be as follows:

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

... But the return of an async method must be a Task? How do you get around that?
the ideas is not to use a constructor, but a static function that constructs the object ans async Initializes it. So don't do the initialization in the constructor, but in a separate private Initialize function, this Initialize function can return an awaitable Task and thus the static Create function can return an awaitable Task
I'm referring to this as the async constructor pattern from now on. -- IMO, this should be the accepted answer, because it's nice, simple, and too the point -- Nice work!
This was very helpful, thanks for sharing! and in enough detail to make it simple to understand. kudos
Tried it with code-behind of XAML View file (Xamarin.Forms) and I suppose this way of solving the problem is not applicable for my context. Thanks for the idea anyway @HaraldCoppoolse . Error makes total sense: Type 'MyClassViewModel' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter.
E
Emir Akaydın

if you make constructor asynchronous, after creating an object, you may fall into problems like null values instead of instance objects. For instance;

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

That's why they don't allow this i guess.


You would think so, but that actually doesn't make sense even. If you make a call like 'var o = sqlcmd.BeginExecuteReader();' it's going to assign an IAsyncResult object to o before it continues to the next line. In your example, it can't assign anything to instance until the constructor is completed so it just doesn't make sense to allow the constructor to be asynchronous.
The way I was expecting (hoping actually, "expect" is too strong a word) it to behave was to return the constructed object, but the object would finish constructing when what it was awaiting was ready. Since I think of await as being more of a set-up-a-continuation-and-then-return, I was hoping this might be possible. I wouldn't expect null to be returned.
Allowing half constructed objects (as is implicit by an async constructor) would break other language constructs, such as the guarantees made by the readonly keyword.
If the constructor of a class C was truly Async you would get a Task that you would have to await.
D
Dmitry Shechtman

In this particular case, a viewModel is required to launch the task and notify the view upon its completion. An "async property", not an "async constructor", is in order.

I just released AsyncMVVM, which solves exactly this problem (among others). Should you use it, your ViewModel would become:

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

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

Strangely enough, Silverlight is supported. :)


t
tsemer

I was just wondering why we can't call await from within a constructor directly.

I believe the short answer is simply: Because the .Net team has not programmed this feature.

I believe with the right syntax this could be implemented and shouldn't be too confusing or error prone. I think Stephen Cleary's blog post and several other answers here have implicitly pointed out that there is no fundamental reason against it, and more than that - solved that lack with workarounds. The existence of these relatively simple workarounds is probably one of the reasons why this feature has not (yet) been implemented.


S
Sylvain Rodrigue

calling async in constructor maybe cause deadlock, please refer to 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


That's about calling an async method from a constructor (which is possible, but probably not a good idea). This question is about the constructor itself being async (which won't compile at all).
Many answers are saying "there's no reason it shouldn't be possible", this is a good reason -- also, if libraries start doing async stuff in their constructors (i.e. even .Wait() or .GetResult()) it can cause other issues; for example ASP.NET web forms requires special configuration for async calls to work (i.e. it's not a deadlock, but the execution context just drops off somewhere and never comes back -- even after configuration, it only works within certain parts of the page life cycle...) -- in general, I think hiding async calls in synchronous methods should be considered an anti-pattern.
j
johnsmith

Some of the answers involve creating a new public method. Without doing this, use the Lazy<T> class:

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

To use Data, use Data.Value.


B
BionicCode

C# doesn't allow async constructors. Constructors are meant to return fast after some brief initialization. You don't expect and you don't want to wait for an instance i.e. the constructor to return. Therefore, even if async constructors were possible, a constructor is not a place for long-running operations or starting background threads. The only purpose of a constructor is initialization of instance or class members to a default value or the captured constructor parameters. You always create the instance and then call DoSomething() on this instance. Async operations are no exception. You always defer expensive initialization of members.

There are a few solutions to avoid the requirement of async constructors.

A simple alternative solution using Lazy or AsyncLazy (requires to install the Microsoft.VisualStudio.Threading package via the NuGet Package Manager). Lazy allows to defer the instantiation or allocation of expensive resources.

public class OrderService
{
  public List<object> Orders => this.OrdersInitializer.GetValue();
  private AsyncLazy<List<object>> OrdersInitializer { get; }

  public OrderService()
    => this.OrdersInitializer = new AsyncLazy<List<object>>(InitializeOrdersAsync, new JoinableTaskFactory(new JoinableTaskContext()));

  private async Task<List<object>> InitializeOrdersAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(5));
    return new List<object> { 1, 2, 3 };
  }
}

public static void Main()
{
  var orderService = new OrderService();

  // Trigger async initialization
  orderService.Orders.Add(4);
}

You can expose the data using a method instead of a property

public class OrderService
{
  private List<object> Orders { get; set; }

  public async Task<List<object>> GetOrdersAsync()
  {
    if (this.Orders == null)
    {
      await Task.Delay(TimeSpan.FromSeconds(5));
      this.Orders = new List<object> { 1, 2, 3 };
    }
    return this.Orders;
  }
}

public static async Task Main()
{
  var orderService = new OrderService();

  // Trigger async initialization
  List<object> orders = await orderService.GetOrdersAsync();
}

Use an InitializeAsync method that must be called before using the instance

public class OrderService
{
  private List<object> orders;
  public List<object> Orders 
  { 
    get
    {
      if (!this.IsInitialized)
      {
        throw new InvalidOperationException(); 
      }
      return this.orders;
    }
    private set
    {
      this.orders = value;
    }
  }

  public bool IsInitialized { get; private set; }

  public async Task<List<object>> InitializeAsync()
  {
    if (this.IsInitialized)
    {
      return;
    }

    await Task.Delay(TimeSpan.FromSeconds(5));
    this.Orders = new List<object> { 1, 2, 3 };
    this.IsInitialized = true;
  }
}

public static async Task Main()
{
  var orderService = new OrderService();

  // Trigger async initialization
  await orderService.InitializeAsync();
}

Instantiate the instance by passing the expensive arguments to the constructor

public class OrderService
{
  public List<object> Orders { get; }

  public async Task<List<object>> OrderService(List<object> orders)
    => this.Orders = orders;
}

public static async Task Main()
{
  List<object> orders = await GetOrdersAsync();

  // Instantiate with the result of the async operation
  var orderService = new OrderService(orders);
}

private static async Task<List<object>> GetOrdersAsync()
{
  await Task.Delay(TimeSpan.FromSeconds(5));
  return new List<object> { 1, 2, 3 };
}

Use a factory method and a private constructor

public class OrderService
{
  public List<object> Orders { get; set; }

  private OrderServiceBase()  
    => this.Orders = new List<object>();

  public static async Task<OrderService> CreateInstanceAsync()
  {
    var instance = new OrderService();
    await Task.Delay(TimeSpan.FromSeconds(5));
    instance.Orders = new List<object> { 1, 2, 3 };
    return instance;
  }
}

public static async Task Main()
{
  // Trigger async initialization  
  OrderService orderService = await OrderService.CreateInstanceAsync();
}

S
Sanjay Patel

you can use Action inside Constructor

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

This creates and uses an async void method, which is not a good idea.
so you have to use Data = GetDataTask().Result;
No. Result could cause deadlocks. I have a variety of solutions described on my blog.
D
Dirk Boer

Please bump this language request:

https://github.com/dotnet/csharplang/discussions/419

The amount of boilerplate code everyone needs to write to have a fully initialized async object is crazy and completely opposite of the trend in C# (less boilerplate).


o
omri tsufim

you can create a wrapper and inject a functor representing the constructor:

class AsyncConstruct<T>
    where T: class
{
    private readonly Task<T> m_construction;
    private T m_constructed;
    public AsyncConstruct(Func<T> createFunc)
    {
        m_constructed = null;
        m_construction = Task.Run(()=>createFunc());
    }

    public T Get()
    {
        if(m_constructed == null)
        {
            m_constructed = m_construction.Result;
        }
        return m_constructed;
    }
}

t
tomcat

I would use something like this.

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

This is as close to I can get for constructors.


u
user1630939

I use this easy trick.

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
    */
  }
}

Not finished when leaving constructor
your still not awaiting the return of the async so this is pointless
B
Brandon Moore

I'm not familiar with the async keyword (is this specific to Silverlight or a new feature in the beta version of Visual Studio?), but I think I can give you an idea of why you can't do this.

If I do:

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

o may not be done initializing before the next line of code runs. An instantiation of your object cannot be assigned until your constructor is completed, and making the constructor asynchronous wouldn't change that so what would be the point? However, you could call an asynchronous method from your constructor and then your constructor could complete and you would get your instantiation while the async method is still doing whatever it needs to do to setup your object.


Moreover, what would it assign to o while it's waiting on the constructor to complete? I know the natural tendency is to think it should be null, but that's not how it works. You would never get null returned without threading... using threading doesn't change that.
think about "var o;" only without "new MyObject()". that's what you get before constructor finishes its job. since making constructor async doesn't seem to be possible, we cannot test atomic times but we can assume it holds the same status with "var o;" only until it gets constructed.
'var o;' is not a valid statement. But let's assume we were specifying the type. In the first line you'd have 'object o;' and the second line would be 'o = new MyObject()'. Now, it HAS to assign something to o before it can go to the next line... there inlies the problem because it can't until the constructor is finished.
it would return Task<MyObject>(), obviously.