How to Implement Async Task to Fetch Data from Database Using Async and Await

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.

To guarantee completion you have two options (if you want to use coroutines).
  1. 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
}

  1. 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.
  1. getUserProfile should not return Deferred<Profile> (especially if it's already suspending), as this is an unsafe primitive to be passing around, it should just return Profile. This way you don't introduce unwanted concurrency by accident and you don't have to write await().
@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
}

  1. 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
}

  1. Last and the least, if you create a CoroutineScope without cancelling it later, you should just use GlobalScope to reduce the number of allocations. People say "Avoid using GlobalScope.", which is true but you should equally avoid creating CoroutineScopes 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;DR

I'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



Leave a reply



Submit