Reputation: 10015
I'm using the LoadState method as example, but this can be seen as a scenario for asynchronous programming in general.
The LoadState
and SaveState
method implementation typically have following signature:
public override void LoadState(..)
public async override void LoadState(..)
You can choose to add the async keyword depending on the fact that you want to await the loading of some data or not. But as the return type of LoadState
is void
, the LoadState method itself can not be awaited and is triggered as fire and forget. This is typically good, because it allows a responsive UI, while being able to use async/await in your data loading logic.
In some scenarios I'd like to await the async LoadState method. However, if I change the signature to Task
instead of void, all my implementations that don't use async logic have to return null (which doesn't seem like the best solution). Is there another possible solution in which some method calls can be awaited and most stay fire and forget (as default)?
Upvotes: 2
Views: 1578
Reputation: 10015
One possible solution I see is defining 2 methods in the base class (and an aditional 3rd for extra logic):
virtual void LoadState(){}
virtual async Task LoadStateAsync{ }
virtual void SomeOtherLogic(){}
Where you would call the LoadState, you now have 2 method calls:
LoadState();
await LoadStateAsync();
SomeOtherLogic();
In this solution you can override the LoadState method if SomeOtherLogic is empty or doesn't require data from the LoadState method. If you do require data, you await the LoadStateAsync method before executing SomeOtherLogic.
Downside is that you (and other developers) always have to pay attention which method is/should be used.
Upvotes: 0
Reputation: 457217
There are several problems with an async void
method:
await
for it to complete (as you noted).async void
called "fire and forget" but I've used the term "fire and crash" myself.Also, you should never return null
from an Async
method. That would be surprising to other coders used to TAP.
You can provide two methods void LoadState
and Task LoadStateAsync
, but if you do this I recommend you always provide both implementations. There is not a good easy way to wrap asynchronous methods in synchronous methods or synchronous methods in asynchronous methods, so you will actually end up with two almost-identical implementations.
I don't particularly like this for maintenance reasons. Personally, I prefer to have a possibly-async
method always have an await
-compatible signature:
interface IWhatever
{
Task LoadStateAsync();
}
Implementations should be asynchronous or fast if they're synchronous. Synchronous implementations can make use of Task.FromResult
:
class Whatever : IWhatever
{
public Task LoadStateAsync()
{
... // fast-running synchronous code
return Task.FromResult<object>(null);
}
}
The consuming code will always be async
:
Whatever whatever = ...;
await whatever.LoadStateAsync();
Note that if the implementation is synchronous, the consuming code will not yield to its caller; it will continue running synchronously through the await
.
Upvotes: 4
Reputation: 171246
You can implement your logic as a Task
-returning method and have LoadState
just discard the return value.
public override void LoadState() { LoadStateInner(); }
Task LoadStateInner() { await ...; }
Now you can call both variants while fulfilling the inheritance contract at the same time.
Upvotes: 0
Reputation: 418
There is a Loaded event I think which gets fired when the loading is complete. Perhaps you can do whatever it is you want to continue with in the event handler for the Loaded event?
Upvotes: 0