This question already has answers here:
Can constructors be async?
(15 answers)
Closed 3 years ago.
I'm needing to call a third party async method from a mvc app. The name of this async method is ForceClient.QueryAsync. It is from an open source project: https://github.com/developerforce/Force.com-Toolkit-for-NET/.
Below works fine, the model.Opportunity contains expected info when the process is in the View stage of the mvc:
public async Task<ActionResult> MyController(string Id) {
. . . .
MyModel model = new MyModel();
var client = new ForceClient(instanceUrl, accessToken, apiVersion);
var qry = await client.QueryAsync<MyModel.SFOpportunity>(
"SELECT Name, StageName FROM Opportunity where Id='" + Id + "'");
model.Opportunity = qry.Records.FirstOrDefault();
. . . .
return View(viewName, myModel);
}
But below does not work. The model.Opportunity is null when the process is in the View stage. I did some debugging and see that the flow goes like this:
1) Step1
2) Step2
3) In the View stage. At this point the model.Opportunity is null, which I need it to be populated.
4) Step3.
public async Task<ActionResult> MyController(string Id) {
. . . .
MyModel myModel = await Task.Run(() =>
{
var result = new MyModel(Id);
return result;
}); // =====> Step 1
. . . .
return View(viewName, myInfoView);
}
public class MyModel
{
public SFOpportunity Opportunity { get; set; }
public MyModel(string id)
{
setOpportunityAsync(id);
}
private async void setOpportunityAsync(string id)
{
. . .
var client = new ForceClient(instanceUrl, accessToken, apiVersion);
var qry = await client.QueryAsync<MyModel.SFOpportunity>(
"SELECT Name, StageName FROM Opportunity where Id='" + id + "'"); // ======> Step2
Opportunity = qry.Records.FirstOrDefault(); // =====> step3
}
So, my question is what do I need to do to get it to execute the steps in the following sequence:
1) Step1
2) Step2
3) Step3
4) In the View stage. At this point the model.Opportunity is should be populated.
You cannot have async constructors.
One alternative is to have async factory methods:
public class MyModel
{
public SFOpportunity Opportunity { get; set; }
private MyModel() { }
public static async Task<MyModel> CreateAsync(string id)
{
var result = new MyModel();
await result.setOpportunityAsync(id);
return result;
}
private async Task setOpportunityAsync(string id)
{
...
}
}
The constructor for MyModel does not (and can not) await setOpportunityAsync because the constructor itself isn't (and can't be) asynchronous. Otherwise you would be able to await the call to the constructor itself, but you can't. So the async method likely won't be finished executing right after the constructor is called. It will be finished... whenever it's finished.
Here's a smaller test class to illustrate the behavior:
public class HasConstructorWithAsyncCall
{
public HasConstructorWithAsyncCall()
{
MarkConstructorFinishedAsync();
}
public bool ConstructorHasFinished { get; private set; }
async void MarkConstructorFinishedAsync()
{
await Task.Delay(500);
ConstructorHasFinished = true;
}
}
What is the value of ConstructorHasFinished immediately after an instance is constructed? Here's a unit test:
[TestMethod]
public void TestWhenConstructorFinishes()
{
var subject = new HasConstructorWithAsyncCall();
Assert.IsFalse(subject.ConstructorHasFinished);
Thread.Sleep(600);
Assert.IsTrue(subject.ConstructorHasFinished);
}
The test passes. The constructor returns while MarkConstructorFinishedAsync hasn't completed, so ConstructorHasFinished is false. Half a second later it finishes, and the value is true.
You can't mark a constructor async so you can't await anything in the constructor.
In general we wouldn't put anything long-running like data retrieval in a constructor, including anything we would call asynchronously. If we do then we must either call it synchronously or know that the completion of the constructor doesn't mean that it's completely "constructed."
Related
If I have a controller with quite a few actions, doing similar things e.g. Gets data from an api, does something with it, returns view with updated model. Is there a better way to handle errors. Currently I do this on a number of action methods, obviously this duplication doesn't feel right but I can't think of an alternative. Thanks
public async Task<IActionResult> method(string id)
{
var result = await _flightRepository.GetLightById(id);
if (!result.Valid)
{
return View("ErrorPage", result.Error.Message);
}
var viewModel = new FlightViewModel
{
Flight = result.Result
};
return View(viewModel);
}
Basically I want to somehow encapsulate the error handling logic to return the error view if valid is false, otherwise populate viewmodel and return view. The valid property returns true if there is an error with the request (this is done in api layer)
Thanks for any help
You should use Filters to reuse code among your actions.
For example in your case your filter could be:
public class FlightValidator: ActionFilterAttribute
{
private readonly string _flightIdRouteKey; // e.g 23
private readonly string _errorViewName; // e.g "ErrorPage"
private readonly IFlightRepository _flightRepo;
public FlightValidator(string flightIdRouteKey, string errorViewName, IFlightRepository flightRepository)
{
_flightIdRouteKey = flightIdRouteKey;
_errorViewName = errorViewName;
_flightRepo = flightRepository;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
int flightId = (int)context.RouteData.Values[_flightIdRouteKey];
var result = await _flightRepo.GetFlightById(flightId);
if (!result.Valid)
{
context.Result = new ViewResult
{
ViewName = _errorViewName,
ViewData = new ViewDataDictionary(result.ViewData)
{
Model = model
}
};
return;
}
await next();
}
}
Now you can use the filter like this:
[TypeFilter(typeof(FlightValidator), Arguments = new object[] { "id", "ErrorPage"})]
public async Task<IActionResult> method(string id)
{
var viewModel = new FlightViewModel
{
Flight = result.Result
};
return View(viewModel);
}
I suggest you to refer Filters docs for more information about filters.
If you use a static method like this:
static async Task<IActionResult> ProcessAsync<T>(
Func<Task<RepositoryResult<T>>> process,
Func<T, IActionResult> ifValid
)
{
var result = await process();
if (!result.Valid)
{
return View("ErrorPage", result.Error.Message);
}
return ifValid(result.Result);
}
Then you can use it like this:
public Task<IActionResult> method(string id)
{
return ProcessAsync(
() => _flightRepository.GetLightById(id),
flight =>
{
var viewModel = new FlightViewModel
{
Flight = flight
};
return View(viewModel);
}
);
}
The static method can be defined anywhere you wish - in a base Controller class, or in a different namespace entirely (in which case you would import it with using static Some.Namespace.With.Class;.
Additionally, I suggest you use the convention of an Async suffix for asynchronous methods - so because GetLightById returns Task<T> I suggest renaming it GetLightByIdAsync.
I have a static class with a async method in it . The async method awaits a user action (user needs to confirms on a dialog). I need to call this method in a constructor in App.xaml.cs (xamarin)
public static class StoragePermission
{
public static async Task Check()
{
try
{
var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Storage);
if (status != PermissionStatus.Granted)
{
var results = await CrossPermissions.Current.RequestPermissionsAsync(new[] { Permission.Storage });
status = results[Permission.Storage];
}
}
catch (Exception ex) {}
}
}
And I have tried calling this method in the constructor like this:
StoragePermission.Check().Wait();
But doing the above , I dont get the next code to invoke at all. After the user action, focus does not return to the code.
I have also tried it this way:
public Task StorageInitialization { get; private set; }
and then within constructor, I do
StorageInitialization = StoragePermission.Check();
But doing this code returns to the next line in constructor, but does not wait for the user action to be completed.
I appreciate if someone could help me with this
I'am trying to run my Controller Action in async way.
How do I use async Task ? Or How to run in async way
// Db context
public class DeptContext : DbContext
{
public LagerContext(DbContextOptions<LagerContext> options)
: base(options)
{
Database.Migrate();
}
public DbSet<Department> Departments { get; set; }
public DbSet<Product> Products { get; set; }
}
// This is my Interface IDepRepository
Task<Department> GetDepartmentWithOrWithoutProducts(int deptId, bool includeProducts);
// And my Repository class DepRepository
public class DepRepository : IDepRepository
{
private DeptContext db;
public DepRepository(DeptContext context)
{
db = context;
}
// I'am geting Department name with products or Without products
public async Task<Department> GetDepartmentWithOrWithoutProducts(int deptId, bool includeProducts)
{
if(includeProductss)
{
return await db.Departments.Include(c => c.Products).Where(s => s.deptId == deptId).SingleAsync();
}
return await db.Departments.Where(s => s.deptId == deptId).SingleAsync();
}
}
So How should I do now in my Controller to do it as async way: I tried as following but I don't know if it's right to do like this following:
I'm not getting any error but I don't if it's right way ...
using System.Threading.Tasks;
using System.Net;
using Microsoft.Data.Entity;
using Microsoft.EntityFrameworkCore;
[Route("api/departments")]
public class DepartmentsController : Controller
{
private IDeptRepository _deptInfoRepository;
public DepartmentsController(IDeptRepository deptInfoRepository)
{
_deptInfoRepository = deptInfoRepository;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetDepatment(int id, bool includeProducts = false)
{
var dept = _deptInfoRepository.GetDepartmentWithOrWithoutProducts(id, includeComputers);
if(dept == null)
{
return BadRequest();
}
if(includeProducts)
{
var depResult = new DepartmentDto() { deptId = dept.deptId, deptName = dept.deptName };
foreach(var department in dept.Products)
{
depResult.Products.Add(new ProductDto() { productId = department.productId, deptId = department.deptId, ProductName = department.ProductName });
}
return Ok(depResult);
}
var departmentWithoutProductResult = new DepartmentsWithoutProductsDto() { DeptId = dept.deptId, DeptName = dept.DeptName};
return Ok(departmentWithoutProductResult);
}
How do I do to get my controller in async way.. I don't know where to put those await and ToListAsync(). Thank you in advance!
The interface should be renamed to better show the intent.
public interface IDepRepository {
Task<Department> GetDepartmentWithOrWithoutProductsAsync(int deptId, bool includeProducts);
//...
}
Which would update the implementation accordingly. Since the method is not actually using anything after the async call then there not really any reason to tag the method as async. Just return the Task.
public Task<Department> GetDepartmentWithOrWithoutProductsAsync(int deptId, bool includeProducts) {
if(includeProductss) {
return db.Departments.Include(c => c.Products).Where(s => s.deptId == deptId).SingleAsync();
}
return db.Departments.Where(s => s.deptId == deptId).SingleAsync();
}
The controller action however needs to await the task and then continue after the task has completed so therefore that method will be tagged with async.
[HttpGet("{id}")]
public async Task<IActionResult> GetDepatment(int id, bool includeProducts = false) {
var dept = await _deptInfoRepository.GetDepartmentWithOrWithoutProductsAsync(id, includeComputers);
if (dept == null) {
return BadRequest();
}
if (includeProducts) {
var depResult = new DepartmentDto() { deptId = dept.deptId, deptName = dept.deptName };
foreach (var department in dept.Products) {
depResult.Products.Add(new ProductDto() {
productId = department.productId,
deptId = department.deptId,
ProductName = department.ProductName
});
}
return Ok(depResult);
}
var departmentWithoutProductResult = new DepartmentsWithoutProductsDto() { DeptId = dept.deptId, DeptName = dept.DeptName};
return Ok(departmentWithoutProductResult);
}
What I can't tell from your code is the datatype of what GetDepartments returns. My guess is that you are using EF Core and GetDepartments returns a DbSet or a LINQ query against a DbSet. If that is the case, then after the line where your depEntities variable is set, that variable points to a deferred object (an expression tree that has not been evaulated yet). Or in other words, the actual query has not been sent to the database yet. When you loop over the depEntities (with your foreach loop), you are causing the actual potentially long-running work to occur (database access). That's what you want to await on. So, yes, you could make an async version of GetDepartments or you could also probably change your code to be:
var depEntities = await _depRepository.GetDepartments().ToListAsync();
The call to ToListAsync will enumerate the deferred object and perform the database access. Your return statement would just return results. Behind the scenes, the method actually returns on your await statement and resumes after the work you're awaiting on completes.
One last note.. any database exceptions will occur at the point where the deferred object is enumerated.
You should not do any await on already-prepared results list. It's already contain required data - what you want to wait to?
You should make new async version of your GetDepartments() method and await while obtaining data from repository:
var depEntities = await _depRepository.GetDepartmentsAsync();
Seeking some input on a behaviour I'm noticing in my code below. This is my first attempt at async/await using Xamarin Forms and I have perused hundreds of posts, blogs and articles on the subject including the writings from Stephen Cleary on async from constructors and best practices to avoid locking. Although I am using a MVVM framework I assume my issue is more generic than that so I'll ignore it for the moment here.
If I am still missing something or there are ways to improve what I'm trying to do ... happy to listen and learn.
At a high level the logic is as follows:
Application starts and initialises
During initialisation verify database exist and if not - create the SQLite DB. Currently I force this every time to simulate a new application and pre-populate it with some sample data for development purposes
After initialisation completed load results set and display
This works most of the time but I have noticed 2 infrequent occurrences due to the async handling of the database initialisation and pre-populating:
Occasionally not all sample records created are displayed once the app started up - I assume this is because the pre-population phase has not completed when the results are loaded
Occasionally I get an error that one of the tables have not been created - I assume this is because the database initialisation has not completed when the results are loaded
The code - simplified to show the flow during initialisation and startup:
----------- VIEW / PAGE MODEL ----------------
public class MyListItemsPageModel
{
private ObservableRangeCollection<MyListItem> _myListItems;
private Command loadItemsCommand;
public MyListItemsPageModel()
{
_myListItems = new ObservableRangeCollection<MyListItem>();
}
public override void Init(object initData)
{
if (LoadItemsCommand.CanExecute(null))
LoadItemsCommand.Execute(null);
}
public Command LoadItemsCommand
{
get
{
return loadItemsCommand ?? (loadItemsCommand = new Command(async () => await ExecuteLoadItemsAsyncCommand(), () => { return !IsBusy; }));
}
}
public ObservableRangeCollection<MyListItem> MyListItems {
get { return _myListItems ?? (_myListItems = new ObservableRangeCollection<MyListItem>()); }
private set {
_myListItems = value;
}
}
private async Task ExecuteLoadItemsAsyncCommand() {
if (IsBusy)
return;
IsBusy = true;
loadItemsCommand.ChangeCanExecute();
var _results = await MySpecificDBServiceClass.LoadAllItemsAsync;
MyListItems = new ObservableRangeCollection<MyListItem>(_results.OrderBy(x => x.ItemName).ToList());
IsBusy = false;
loadItemsCommand.ChangeCanExecute();
}
}
----------- DB Service Class ----------------
// THERE IS A SPECIFIC SERVICE LAYER BETWEEN THIS CLASS AND THE PAGE VIEW MODEL HANDLING THE CASTING OF TO THE SPECIFIC DATA TYPE
// public class MySpecificDBServiceClass : MyGenericDBServiceClass
public class MyGenericDBServiceClass<T>: IDataAccessService<T> where T : class, IDataModel, new()
{
public SQLiteAsyncConnection _connection = FreshIOC.Container.Resolve<ISQLiteFactory>().CreateConnection();
internal static readonly AsyncLock Mutex = new AsyncLock();
public DataServiceBase()
{
// removed this from the constructor
//if (_connection != null)
//{
// IsInitialized = DatabaseManager.CreateTableAsync(_connection);
//}
}
public Task<bool> IsInitialized { get; private set; }
public virtual async Task<List<T>> LoadAllItemsAsync()
{
// Temporary async/await initialisation code. This will be moved to the start up as per Stephen's suggestion
await DBInitialiser();
var itemList = new List<T>();
using (await Mutex.LockAsync().ConfigureAwait(false))
{
itemList = await _connection.Table<T>().ToListAsync().ConfigureAwait(false);
}
return itemList;
}
}
----------- DB Manager Class ----------------
public class DatabaseManager
{
static double CURRENT_DATABASE_VERSION = 0.0;
static readonly AsyncLock Mutex = new AsyncLock();
private static bool IsDBInitialised = false;
private DatabaseManager() { }
public static async Task<bool> CreateTableAsync(SQLiteAsyncConnection CurrentConnection)
{
if (CurrentConnection == null || IsDBInitialised)
return IsDBInitialised;
await ProcessDBScripts(CurrentConnection);
return IsDBInitialised;
}
private static async Task ProcessDBScripts(SQLiteAsyncConnection CurrentConnection)
{
using (await Mutex.LockAsync().ConfigureAwait(false))
{
var _tasks = new List<Task>();
if (CURRENT_DATABASE_VERSION <= 0.1) // Dev DB - recreate everytime
{
_tasks.Add(CurrentConnection.DropTableAsync<Table1>());
_tasks.Add(CurrentConnection.DropTableAsync<Table2>());
await Task.WhenAll(_tasks).ConfigureAwait(false);
}
_tasks.Clear();
_tasks.Add(CurrentConnection.CreateTableAsync<Table1>());
_tasks.Add(CurrentConnection.CreateTableAsync<Table2>());
await Task.WhenAll(_tasks).ConfigureAwait(false);
_tasks.Clear();
_tasks.Add(UpgradeDBIfRequired(CurrentConnection));
await Task.WhenAll(_tasks).ConfigureAwait(false);
}
IsDBInitialised = true;
}
private static async Task UpgradeDBIfRequired(SQLiteAsyncConnection _connection)
{
await CreateSampleData();
return;
// ... rest of code not relevant at the moment
}
private static async Task CreateSampleData()
{
IDataAccessService<MyListItem> _dataService = FreshIOC.Container.Resolve<IDataAccessService<MyListItem>>();
ObservableRangeCollection<MyListItem> _items = new ObservableRangeCollection<MyListItem>(); ;
_items.Add(new MyListItem() { ItemName = "Test 1", ItemCount = 14 });
_items.Add(new MyListItem() { ItemName = "Test 2", ItemCount = 9 });
_items.Add(new MyListItem() { ItemName = "Test 3", ItemCount = 5 });
await _dataService.SaveAllItemsAsync(_items).ConfigureAwait(false);
_items = null;
_dataService = null;
IDataAccessService<Sample> _dataService2 = FreshIOC.Container.Resolve<IDataAccessService<AnotherSampleTable>>();
ObservableRangeCollection<Sample> _sampleList = new ObservableRangeCollection<Sample>(); ;
_sampleList.Add(new GuestGroup() { SampleName = "ABC" });
_sampleList.Add(new GuestGroup() { SampleName = "DEF" });
await _dataService2.SaveAllItemsAsync(_sampleList).ConfigureAwait(false);
_sampleList = null;
_dataService2 = null;
}
}
In your DataServiceBase constructor, you're calling DatabaseManager.CreateTableAsync() but not awaiting it, so by the time your constructor exits, that method has not yet completed running, and given that it does very little before awaiting, it's probably barely started at that point. As you can't effectively use await in a constructor, you need to remodel things so you do that initialisation at some other point; e.g. perhaps lazily when needed.
Then you also want to not use .Result/.Wait() whenever possible, especially as you're in an async method anyway (e.g. ProcessDBScripts()), so instead of doing
var _test = CurrentConnection.DropTableAsync<MyListItem>().Result;
rather do
var _test = await CurrentConnection.DropTableAsync<MyListItem>();
You also don't need to use Task.Run() for methods that return Task types anyway. So instead of
_tasks.Add(Task.Run(() => CurrentConnection.CreateTableAsync<MyListItem>().ConfigureAwait(false)));
_tasks.Add(Task.Run(() => CurrentConnection.CreateTableAsync<AnotherSampleTable>().ConfigureAwait(false)));
just do
_tasks.Add(CurrentConnection.CreateTableAsync<MyListItem>()));
_tasks.Add(CurrentConnection.CreateTableAsync<AnotherSampleTable>()));
sellotape has correctly diagnosed the code problem: the constructor is starting an asynchronous method but nothing is (a)waiting for it to complete. A simple fix would be to add await IsInitialized; to the beginning of LoadAllItemsAsync.
However, there's also a design problem:
After initialisation completed load results set and display
That's not possible on Xamarin, or any other modern UI platform. You must load your UI immediately and synchronously. What you should do is immediately display a splash/loading page and start the asynchronous initialization work. Then, when the async init is completed, update your VM/UI with your "real" page. If you just have LoadAllItemsAsync await IsInitialized, then your app will sit there for some time showing the user zero data before it "fills in".
You may find my NotifyTask<T> type (available on NuGet) useful here if you want to show a splash/spinner instead of zero data.
I have a controller action in mvc 4 app:
public ActionResult Index()
{
GetStartedModel gsModel = new GetStartedModel();
return View(gsModel);
}
and ViewModel:
public class GetStartedModel
{
public IEnumerable<SelectListItem> listA { get; set; }
public IEnumerable<SelectListItem> listB { get; set; }
public GetStartedModel()
{
TestDataWebServiceHelper service = new TestDataWebServiceHelper();
this.GetData(service);
}
private async void SetData(TestDataWebServiceHelper service)
{
listA = await this.SetListA(service);
listB = await this.SetListB(service);
}
private async Task<IEnumerable<SelectListItem>> SetListA(TestDataWebServiceHelper service)
{
List<String> rawList = new List<String>();
rawList = await service.GetValuesAsync("json");
return rawList.Select(x => new SelectListItem { Text = x, Value = x });
}
private async Task<IEnumerable<SelectListItem>> SetListB(TestDataWebServiceHelper service)
{
List<String> rawList = new List<String>();
rawList = await service.GetValuesAsync("json");
return rawList.Select(x => new SelectListItem { Text = x, Value = x });
}
}
When I call this controller action I receive following error:
An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%# Page Async="true" %>.
So, questions:
Should I somehow mark controller or action or page itself as asynchronous to allow this model initialization?
Is it possible to encapsulate all initialization logic to viewmodel and not to pop it to the controller?
What is the reason of that error? Seem like it related to WebForms, but I use Razor engine.
There are two problems in your code:
You shouldn't start async void operations from constructor like this. In fact, you usually shouldn't start any async operations from constructor and you also shouldn't use async void methods at all (except for event handlers).
I think that in your case, async factory method instead of an constructor makes the most sense:
private GetStartedModel()
{}
public static async Task<GetStartedModel> Create()
{
var service = new TestDataWebServiceHelper();
var result = new GetStartedModel();
listA = await result.SetListA(service);
listB = await result.SetListB(service);
return result;
}
For more details, see Stephen Cleary's post on async constructors.
You need to make your controller action async too:
public async Task<ActionResult> Index()
{
var gsModel = await GetStartedModel.Create()
return View(gsModel);
}
There a few things to take care about "async" are as follows :
The method should only be "async" when it needs to wait for the action to be performed by the code written in that.
if there is nothing to be "await" in the method, than that method should not declared as "async".
if there is any event (or else event handler) to be marked as async then return type should be "void" or any specific return type
but if any other (general) method should marked as "async" then the return type should be Task or Task< return-type > .
Now coming to your questions,
definitely you can mark "Controller", "Event" & "Page" as async (I'm
not damn sure about marking the page as async coz i never used it)
and by that those method will take rest until the action will be
performed completely written in that method.
And that's the actual system to encapsulate whole the initialization
logic in a viewModel.
for that make a Floder, named it as "ViewModels" and put all of the viewModel Codes in that folder and whenever needed, use that.
You should put your this code in a ViewModel:
private async void SetData(TestDataWebServiceHelper service)
{
listA = await this.SetListA(service);
listB = await this.SetListB(service);
}
private async Task<IEnumerable<SelectListItem>> SetListA(TestDataWebServiceHelper service)
{
List<String> rawList = new List<String>();
rawList = await service.GetValuesAsync("json");
return rawList.Select(x => new SelectListItem { Text = x, Value = x });
}
private async Task<IEnumerable<SelectListItem>> SetListB(TestDataWebServiceHelper service)
{
List<String> rawList = new List<String>();
rawList = await service.GetValuesAsync("json");
return rawList.Select(x => new SelectListItem { Text = x, Value = x });
}
and then you should call it whenever needed.
(Hope this helps...)