How to implement Async Task to fetch data from database using async and await?
In order to use the await
keyword properly, the object being 'awaited' should really be an ...Async
method, like the GetStringAsync
method. As @ken2k has correctly pointed out, you cannot just await
any method. Therefore, to answer your question is this a recommended way to do this?, the answer is no.
You can find out how to use the await
and async
keywords correctly in the Asynchronous Programming with Async and Await (C# and Visual Basic) page on MSDN, however, if you're just trying to run a synchronous method asynchronously, then you can do that like this:
public DataTable LoadData()
{
DataTable dtAdditionsDetails = ...
// Get your data here synchronously
return dtAdditionsDetails;
}
public async Task<DataTable> LoadDataAsync()
{
DataTable dtAdditionsDetails = LoadData();
return Task.Run(() => LoadData());
}
...
public async void GetDataAsynchronously()
{
DataTable dtAdditionsDetails = await LoadDataAsync();
}
Note that ...Async
methods (usually) return Task<T>
objects, rather than nothing and that their names end in the word Async
. Also note that only the data is returned from ...Async
methods and not the Task<T>
and that we don't await
when there is nothing to await
.
What's the benefit of using async to return data from database?
On the server side (e.g., WebAPI), async
methods allow the request thread to return to the thread pool while the database server is generating the response. This allows you to scale better.
When the database server returns the response to your WebAPI method (i.e., the task returned by FirstOrDefaultAsync
completes), then ASP.NET will grab another thread from the thread pool and resume processing your request.
Note that not every method should be async
. You should only use async
when you want to await
some operation. There's a great Channel9 video that describes the concepts and benefits of using async
on ASP.NET. I also gave a talk at ThatConference this year on async
on the server side (link to my blog post including slides).
Using async/await to render information only after db fetching function
Try moving your resolve(true);
call to the end of your firebaseRef.on
function.
What is happening now is, as firebaseRef.on
is an async function and resolve
is written after this function not inside of it, resolve(true); will run as soon as getFromDB
is run.
// Get all books from the FirebaseDB and synchs with myLibrary Array
function getFromDB() {
return new Promise((resolve, reject) => {
myLibrary.length = 0; // clears original stored array to get all books again
firebaseRef.on("value", function (data) {
data.forEach((book) => {
//gets each book from firebase creates new book Object to array in the correct order(chronologically)
property = book.val();
let addedBook = new Book(
property.title,
property.author,
property.pages,
property.read,
book.key
);
myLibrary.push(addedBook);
});
// resolve moved inside
resolve(true);
^^^^^^^^^^^^
});
});
}
async function initialRender() {
await getFromDB();
render();
}
Get data from room db using async & await
There are a couple things wrong here but I'll first address the main issue.
fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
CoroutineScope(Dispatchers.IO).launch {
val userProfile = profileDao.getUserProfile().await()
}
return userProfile
}
In getUserFromDB
, the launch
executes asynchronously from getUserFromDB
and will not complete before you return.
- Mark
getUserFromDB
as a suspend function. (Highly recommended)
suspend fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
val userProfile = withContext(Dispatchers.IO) {
profileDao.getUserProfile().await()
}
return userProfile
}
- Use
runBlocking
.
fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
val userProfile = runBlocking(Dispatchers.IO) {
profileDao.getUserProfile().await()
}
return userProfile
}
Now the main issue is addressed. There are a couple things here that break conventions and guidelines.getUserProfile
should not returnDeferred<Profile>
(especially if it's already suspending), as this is an unsafe primitive to be passing around, it should just returnProfile
. This way you don't introduce unwanted concurrency by accident and you don't have to writeawait()
.
@Query("SELECT * FROM PROFILE LIMIT 1")
suspend fun getUserProfile(): Profile
fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
CoroutineScope(Dispatchers.IO).launch {
val userProfile = profileDao.getUserProfile() /* .await() */
}
return userProfile
}
- When using coroutines with Room, it executes blocking calls in a dedicated thread pool, so you do not have to use one (
Dispatchers.IO
), it is safe to call in the main thread/dispatcher. More info in this answer Why does the querySkuDetails need to run in IO context? .
fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
CoroutineScope(/* Dispatchers.IO */).launch {
val userProfile = profileDao.getUserProfile().await()
}
return userProfile
}
- Last and the least, if you create a
CoroutineScope
without cancelling it later, you should just useGlobalScope
to reduce the number of allocations. People say "Avoid usingGlobalScope
.", which is true but you should equally avoid creatingCoroutineScope
s without cancelling them (I'd argue it's even worse).
fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
GlobalScope.launch(Dispatchers.IO) {
val userProfile = profileDao.getUserProfile().await()
}
return userProfile
}
TL;DRI've structured my answer so that you can apply each change independently when you are ready.
If you just want the sum of all the changes then here it is.
@Query("SELECT * FROM PROFILE LIMIT 1")
suspend fun getUserProfile(): Profile
suspend fun getUserFromDB(): Profile {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
val userProfile = profileDao.getUserProfile()
return userProfile
}
Happy coding. :)
Where to use Async Await with databases? In high-level or low-level module?
In most cases, you should not use Task.Run
on ASP.NET at all. In this case in particular, you should definitely not use Task.Run
, because it's just running synchronous code on a background thread - what I call "fake asynchronous".
I recommend starting using async at the lowest level, and working your way up from there. In this case:
var del = await dbConnection.ExecuteAsync(cmd, new { Id = id, Email = email });
Then the compiler will give you an error saying you should mark DeleteUser
as async
and change its return type to Task<bool>
. You'll also need to change the IUsers
interface to do this:
async Task<bool> IUsers.DeleteUserAsync(int id, string email)
and update everything that calls it, leaving your controller method looking like:
bool del = await user.DeleteUserAsync(id, email);
Related Topics
How to Make Blazor Http Get Json Async Request
How to Rename Keys Within a Json File
How to Call Wcf Service Method from Postman
How to Open the Default Chrome Profile Through Selenium, Chromedriver and Googlechrome
Visual Studio Solution Unavailable (Reload Doesn't Work)
How to Properly Make a Http Web Get Request
Sorting a Collection Containing Strings And/Or Numbers
Asp.Net Core Get Json Array Using Iconfiguration
How to Pass Multiple Arguments in Processstartinfo
Programmatically Close Aspx Page from Code Behind
How to Add/Update Child Entities When Updating a Parent Entity in Ef
C# Foreach (Property in Object)... Is There a Simple Way of Doing This
C# - How to Loop Through a Table to Update Each Row MySQL
Drawing a Triangle With Rounded Corners
Generate C# Class from SQL Server Table Without Store Procedure
How to Read Request Body in an ASP.NET Core Webapi Controller
How to Turn Off Brackets/Quotes Auto-Completion in Visual Studio