✅ run async funtion from constructor
we can't use await keyword in the constructor because we can't add
async
keyword to it. How to run a function that returns a Task asynchronously then from the constructor? Or do we have to run it synchronously in that case?
I remember vaguely that there was an old legacy way in .Net to run Tasks asynchronously without the await
keyword. I believe it was ContinueWith()
, is that what we wanna do in that case?
If we do run it synchronously instead, what is the best way to run a Task synchronously out of the many possible existing ways? There is Task.Result
, Task.Wait()
Task.GetAwaiter().GetResult()
, Task.RunSynchronously()
and probably 20 more ways I don't know yet xD which one is generally prefered?36 Replies
Short answer: you can't wait for an async task inside a constructor
what you can do, it make the constructor private, and add a static build/create method
Instead of:
Do something like:
thats a nice workaround but I'd like to keep my constructor 😦
public
So, you want the constructor to return before the operation has finished, or after?
it's a really really really good practice, that after the constructor is ran, the object is valid.
So avoid
Because you will forget or call it multiple times
ideally after
Then your constructor needs to block until the operation has finished, which is really really bad practice
Calling a constructor should never be expensive
(and it should never fail in inconsistent ways, in the way that a database query might)
I don't think it needs to be blocking tho. Can't we use ContinueWith() instead of
await
keyword to run the Task asynchronously without blocking?But then your constructor will return before the operation is complete
Which is why I asked that question 🙂
ContinueWith will chain the task with a next task that will continue after one is done, it's a different use case
You can do exactly the same by calling an
async void
method from your constructor
(without the pain of ContinueWith
)e.g.
are you sure about this? My understanding is that ContinueWith waits for the Task to finish without blocking just like await does and then runs the continuation just like await would
kinda
and it returns the new task that will wait for both to complete
I'm very very sure that
ContinueWith
returns before the Task being awaited completesdoesn't seem like it tho
but you can't:
Which means that your constructor returns before the operation has completed
ohh right
the continuation I'd like to run is the rest of constructor which isnt possible lol
😄
mhm
I mean, you can put some of the ctor code into the continuation... But your actual constructor is going to return before that code is run
That, after all, is the whole freaking point of tasks and async
@canton7 you suggest same workaround like this as @Tvde1 suggested?
The correct thing to do is use a factory method
It's not a "workaround"
Constructors must be cheap, and they must not throw because of things unrelated to their input
A constructor which waits for a DB call, and might randomly fail because of e.g. network stuff, is very bad
The correct thing to do is to fetch the data first, and then when you've got the data, construct the object from the data
And that's what the factory method does
true, I understand. Thank you!
thank you too @Tvde1
No problem :)
👍
even tho I won't go with running synchronously / potentially blocking, could you still answer the last part of my question pls guys? 🙂
what is the best way to run a Task synchronously out of the many possible existing ways? There is Task.Result, Task.Wait() Task.GetAwaiter().GetResult(), Task.RunSynchronously() and probably 20 more ways I don't know yet xD which one is generally prefered?They all seem to be doing the same thing to me, They run it synchronously / potentially blocking. Does it matter which one to use?
Task.GetAwaiter().GetResult()
is best. Task.Wait()
and Task.Result
do the same thing (Task.Result
is only available on Task<T>
), but the gotcha is that if an exception happens, both Task.Wait()
and Task.Result
will wrap it in an AggregateException
, which is annoying. Task.GetAwaiter().GetResult()
doesn't do this, and just throws the original exception
Of course, it can still deadlock, so ideally don't call it at all
Task.RunSynchronously
is something different. I don't think there are any other ways of doing it?awesome, thank you
I'm aware 🙂
I see, I thought based of the name it would be similar to the other things
The issue now is, how is my DI / IoC Container supposed to find the constructor now to inject the dependencies? Can I have it invoke
CreateAsync
instead of ctor somehow?
@canton7 @Tvde1I think I would get/re-use the access token on every request I do
e.g. how long is the access token valid for
An here we go down the DI rabbit hole 😄
The answer with any DI problem like this is "more factories"
so
where
GetOrCreateAccessToken
remembers the one it already has and checks whether it's still valid
I would probably DI an IAccessTokenProvider
which may be a singleton or something :)
(If you just want to take what you've got, although I'd listen to @Tvde1)
To check whether the Token is still valid:
the AccessToken I receive from the Api has a "LifeTime" field. For checking if its still valid, I'd have to sum up DateTime.Now (when token was received) + it's lifetime and check if that is less than or equal to DateTime.Now right? if so we can safely reuse token, otherwise we generate new token?
yeah something like that
still better than a constructor that could crash
I didn't say that was a bad thing 😃
/closed