Multithreading Process With Real-Time Updates In ASP.NET Core Web Application

In today's article, we will see how to implement multithreading with real-time updates shown in ASP.NET Core 2.0 web application.

In today's article, we will see how to implement multithreading with real-time updates shown in ASP.NET Core 2.0 web application.

Why do we need this?

In real-world applications, once in a while, we are faced with an issue of having to process too much data. Real life examples are

  • Processing hundreds of records at once, the user has a checkbox and can check as many he/she wants.
  • There might be a procedure which takes a few minutes to execute for a single record and you have to process multiple records at once.

In all of these situations, you have to think about the processing time and database timeout. So, for these issues, we can use multithreading. We can execute our time-consuming methods in a separate thread so that the processing of the main thread is not blocked and all the records are processed.

Let’s get started.

Here is the DB structure and Stored Procedures (Microsoft SQL Server).

Table structure of MultiProcessStatus

ASP.NET Core

  1. GetMultiprocessStatus Stored procedure    
  2. SET ANSI_NULLS ON    
  3. GO    
  4. SET QUOTED_IDENTIFIER ON    
  5. GO    
  6. -- =============================================    
  7. -- Author:      <Author,,Name>    
  8. -- Create date: <Create Date,,>    
  9. -- Description: <Description,,>    
  10. -- =============================================    
  11. ALTER PROCEDURE [dbo].[GetMultiprocessStatus]    
  12. (    
  13. @UserId NVARCHAR(200),    
  14. @Module NVARCHAR(200)    
  15. )    
  16. AS    
  17. BEGIN    
  18.     SELECT TOP 1 * FROM MultiProcessStatus WHERE UserId = @UserId AND Module = @Module ORDER BY Id DESC    
  19. END    
  20.     
  21.     
  22. InsertMultiprocessStatus Stored procedure    
  23. SET ANSI_NULLS ON    
  24. GO    
  25. SET QUOTED_IDENTIFIER ON    
  26. GO    
  27. -- =============================================    
  28. -- Author:      <Author,,Name>    
  29. -- Create date: <Create Date,,>    
  30. -- Description: <Description,,>    
  31. -- =============================================    
  32. ALTER PROCEDURE [dbo].[InsertMultiprocessStatus]    
  33. (    
  34. @UserId NVARCHAR(200),    
  35. @Module  NVARCHAR(200),    
  36. @TotalRecords INT,    
  37. @ReturnId INT OUTPUT    
  38. )    
  39. AS    
  40. BEGIN    
  41.     SET @ReturnId = 0    
  42.     INSERT INTO MultiProcessStatus (UserId,Module,TotalRecords,FailedRecords,SuccessRecords,Percentage,IsCompleted,CreatedDate)    
  43.     VALUES (@UserId,@Module,@TotalRecords,0,0,0,0,GETDATE())    
  44.     
  45.     SET @ReturnId = @@IDENTITY    
  46. END    
  47.     
  48. UpdateMultiprocessStatus Stored procedure    
  49. SET ANSI_NULLS ON    
  50. GO    
  51. SET QUOTED_IDENTIFIER ON    
  52. GO    
  53. -- =============================================    
  54. -- Author:      <Author,,Name>    
  55. -- Create date: <Create Date,,>    
  56. -- Description: <Description,,>    
  57. -- =============================================    
  58. ALTER PROCEDURE [dbo].[UpdateMultiprocessStatus]     
  59. (    
  60. @Id INT,    
  61. @Percentage DECIMAL(18,2),    
  62. @IsCompleted BIT,    
  63. @FailedRecords INT,    
  64. @SuccessRecords INT    
  65. )    
  66. AS    
  67. BEGIN    
  68.     IF(@Percentage > 99)    
  69.     BEGIN    
  70.         SET @IsCompleted = 1    
  71.     END    
  72.     UPDATE MultiProcessStatus SET Percentage = @Percentage,IsCompleted = @IsCompleted,    
  73.     FailedRecords = @FailedRecords,SuccessRecords = @SuccessRecords     
  74.     WHERE Id = @Id     
  75. END    

Now, we will create our ASP.NET Core 2.0 Web Application. We will select ASP.NET Core Web Application which will be under .NET Core.

ASP.NET Core

Then, we will select the Web Application(Model-View-Controller) as our template.

ASP.NET Core

Once our project is created, we will create a new controller (UserController in this case) and work on that.

Now, first of all, we will make some HTML, CSS, and jQuery changes to show our progress bar which will show the status of multithreading process.

Add this piece of HTML in _layout.cshtml just below where <nav> tag ends.

  1. <div class="row">  
  2.         <div class="col-md-12 col-sm-12 col-xs-12 col-lg-12">  
  3.             <div id="myProgress" class="progress nodisplay">  
  4.                 <div id="myProgressBar" class="progress-bar-striped progress-bar-animated"></div>  
  5.                 <div id="emptyProgress">  
  6.                     <div class="fast-loader"></div>  
  7.                 </div>  
  8.             </div>  
  9.         </div>  
  10.     </div>  

Once you have done that, place this HTML just below the <div class=”container”> ends.

  1. <div id="snackbar"></div>  

When you are done with that, place this piece of the script just above @RenderSection(“Scripts”,required:false).

  1. <script>  
  2.         var multiProcessStatusGetUrl = "@Url.Action("GetMultiProcessStatus","User")";  
  3.         var userId = "Ravi";  
  4.         var moduleName = "User";  
  5.     </script>  

This script section just defines a URL and two variables which we will use in the AJAX call to get the status of multithreading process. (Will be explained later)

Now we have to define some CSS but I will not post the CSS here because it's not so much relevant. However, I will leave a link for the entire code for you guys to download.

Now, we will create a view in our user controller and will add a button. On the click event of this button, we will start our heavy processes.

For database operations, I have used the factory pattern with lazy loading, I will not get into the details of the architecture, I have already written an article explaining this architecture. Click here to read that article.

Now, we will add a Test method in our UserController which will be called to perform heavy operations.

  1. public JsonResult Test()  
  2.         {  
  3.             var isDone = true;  
  4.             var msg = "Process submitted";  
  5.             Task.Factory.StartNew(() =>  
  6.             {  
  7.                 var conStr = appSettings.Value.DbConn;  
  8.                 var client = DbClientFactory<MultiprocessDbClient>.Instance;  
  9.                 var totalRecords = 50;  
  10.                 var id = client.InsertMultiprocessStatus(conStr, "Ravi""User", totalRecords);  
  11.                 int success = 0, fail = 0;  
  12.                 var isComplete = false;  
  13.                 for (int i = 0; i < totalRecords; i++)  
  14.                 {  
  15.                     isComplete = false;  
  16.                     Thread.Sleep(500);  
  17.                     decimal percentage = Convert.ToDecimal((Convert.ToDecimal(i + 1) / Convert.ToDecimal(totalRecords))) * Convert.ToDecimal(100);  
  18.                     success++;  
  19.                     if ((success + fail) == totalRecords)  
  20.                         isComplete = true;  
  21.                     client.UpdateMultiprocessStatus(conStr, id, percentage, isComplete, fail, success);  
  22.                 }  
  23.             });  
  24.             var obj = new { isSuccess = isDone, returnMessage = msg };  
  25.             return Json(obj);  
  26.         }   

In the above method, I have created a new thread with Task.Factory.StartNew.

Inside the body of new thread I have declared a client, and then inserted a multiprocess request in our database with parameters as connectionString, username i.e Ravi (hardcoded for testing purpose, you will pass it as the user id of the logged in user).

Next is  moduleName i.e User (again hardcoded for testing purpose, you will pass the module on which user is going to perform the action such as User, Employee, Leave etc.).

Next is total records (these will be the total number of records that you need to processed, generally a list will be posted so this will be the list.Count)

Now. I have written a simple for loop to iterate up to my total records (in real case scenario you will be iterating through each item of the list posted by the user). Then, I have written Thread.Sleep(500). 

This is just to replicate a database operation where you will take values from an item of the list and post them to your database. Now once the operation is completed, then you will get the result whether its failure or success.

Then, based on that output, increase the number of your fail/success variable and then calculated percentage. After that, we will update the multiprocess status with the id that we got from the inserted method, percentage, isComplete which is computed by checking whether success plus fail equals total or not.

And after the thread, I have returned a JSON object with simply a message as the process has been submitted because this JSON will be returned and our new thread will be executing in parallel.

We will create a MultiprocessModel.

  1. [DataContract]  
  2.     public class MultiprocessModel  
  3.     {  
  4.         [DataMember(Name = "Id")]  
  5.         public int Id { getset; }  
  6.   
  7.         [DataMember(Name = "UserId")]  
  8.         public string UserId { getset; }  
  9.   
  10.         [DataMember(Name = "Module")]  
  11.         public string Module { getset; }  
  12.   
  13.         [DataMember(Name = "TotalRecords")]  
  14.         public int TotalRecords { getset; }  
  15.   
  16.         [DataMember(Name = "FailedRecords")]  
  17.         public int FailedRecords { getset; }  
  18.   
  19.         [DataMember(Name = "SuccessRecords")]  
  20.         public int SuccessRecords { getset; }  
  21.   
  22.         [DataMember(Name = "Percentage")]  
  23.         public decimal Percentage { getset; }  
  24.   
  25.         [DataMember(Name = "IsCompleted")]  
  26.         public bool IsCompleted { getset; }  
  27.     }  

Now, let's see my ProcessingDbClient class.

  1. public class MultiprocessDbClient  
  2.     {  
  3.         public int InsertMultiprocessStatus(string connString, string userId, string module, int totalRecords)  
  4.         {  
  5.             var outParam = new SqlParameter("@ReturnId", SqlDbType.Int)  
  6.             {  
  7.                 Direction = ParameterDirection.Output  
  8.             };  
  9.             SqlParameter[] param = {  
  10.                 new SqlParameter("@UserId",userId),  
  11.                 new SqlParameter("@Module",module),  
  12.                 new SqlParameter("@TotalRecords",totalRecords),  
  13.                 outParam  
  14.             };  
  15.             SqlHelper.ExecuteProcedureReturnString(connString, "InsertMultiprocessStatus", param);  
  16.   
  17.             return (int)outParam.Value;  
  18.         }  
  19.   
  20.         public void UpdateMultiprocessStatus(string connString, int id, decimal percentage, bool isCompleted,  
  21.             int failRecords, int successRecords)  
  22.         {  
  23.             SqlParameter[] param = {  
  24.                 new SqlParameter("@Id",id),  
  25.                 new SqlParameter("@Percentage",percentage),  
  26.                 new SqlParameter("@IsCompleted",isCompleted),  
  27.                 new SqlParameter("@FailedRecords",failRecords),  
  28.                 new SqlParameter("@SuccessRecords",successRecords),  
  29.             };  
  30.             SqlHelper.ExecuteProcedureReturnString(connString, "UpdateMultiprocessStatus", param);  
  31.         }  
  32.   
  33.         public MultiprocessModel GetMultiprocessStatus(string connString ,string userId,string moduleName)  
  34.         {  
  35.             SqlParameter[] param = {  
  36.                 new SqlParameter("@UserId",userId),  
  37.                 new SqlParameter("@Module",moduleName)  
  38.             };  
  39.             return SqlHelper.ExtecuteProcedureReturnData<MultiprocessModel>(connString,  
  40.                "GetMultiprocessStatus", r => r.TranslateAsMultiprocess(), param);  
  41.         }  
  42.     }  

Last method in this client will return the process status model on the basis of username and module name

Now we will add a method in our controller to call this last method for status of our multiprocessing operation,

  1. [HttpPost]  
  2.         public JsonResult GetMultiProcessStatus([FromBody]MultiprocessModel model)  
  3.         {  
  4.             var data = DbClientFactory<MultiprocessDbClient>.Instance.GetMultiprocessStatus(  
  5.                 appSettings.Value.DbConn, model.UserId, model.Module);  
  6.             var response = new Message<MultiprocessModel>();  
  7.             response.IsSuccess = true;  
  8.             response.Data = data;  
  9.             return Json(response);  
  10.         }  

This is the method which we declared in _layout.cshtml as URL. Now, we will make a JavaScript file and add it in our Index view of UserController, so our Index will look something like this.

ASP.NET Core

I have added a folder inside js folder with name User just to manage my code a bit better. Now, we will add some generic methods to our site.js such as to show the progress bar, to make the AJAX calls etc.

  1. function showSnackbar(msg) {  
  2.     if (msg != '' && msg != null && msg != undefined) {  
  3.         $('#snackbar').html(msg);  
  4.         $('#snackbar').addClass('show');  
  5.   
  6.         setTimeout(function () {  
  7.             $('#snackbar').removeClass('show');  
  8.         }, 3000);  
  9.     }  
  10. }  

The above method is just to show a snackbar/notification.

  1. //Global method to perform ajax calls  
  2. function makeAjaxCall(url, data, loaderId, type) {  
  3.     return $.ajax({  
  4.         url: url,  
  5.         type: type ? type : "GET",  
  6.         data: data,  
  7.         contentType: "application/json",  
  8.         beforeSend: function () { showLoader(loaderId); },  
  9.         complete: function () { hideLoader(loaderId); }  
  10.     });  
  11. }  
  12.   
  13. function showLoader(id) {  
  14.     if (!id)  
  15.         $("#fullLoader").addClass("loader");  
  16. }  
  17.   
  18. function hideLoader(id) {  
  19.     if (!id)  
  20.         $('#fullLoader').removeClass('loader');  
  21. }  

The first method is for making AJAX calls with showLoader and hideLoader functions. These two functions are just to show a full page loader.

  1. function showMultiThreadingProgressBar() {  
  2.     $("#myProgress").removeClass("nodisplay");  
  3.     $("#myProgress #myProgressBar").width("1%");  
  4.     $("#myProgress #emptyProgress").width("99%");  
  5. }  

The above method is to show our multithreading progress bar.

  1. function updateProgress(percentageBar) {  
  2.     $("#myProgress").removeClass("nodisplay");  
  3.     //Show progress  
  4.   
  5.     $("#myProgress #myProgressBar").width((percentageBar) + "%");  
  6.     $("#myProgress #emptyProgress").width((100 - percentageBar) + "%");  
  7.     isOperationInprogress = true;  
  8.   
  9.     if (percentageBar == 100) {  
  10.         //Process complete message  
  11.         isOperationInprogress = false;  
  12.         setTimeout(function () {  
  13.             $("#myProgress").addClass("nodisplay");  
  14.         }, 800);  
  15.     }  
  16. }  

The above method is to update progress bar according to the percentage completed.

  1. function getMultiProcessStatus(isOnLoad) {  
  2.     var obj = { UserId: userId, Module: moduleName }  
  3.     makeAjaxCall(multiProcessStatusGetUrl, JSON.stringify(obj), "nsnn"'POST')  
  4.         .done(function (response) {  
  5.             if (!response.IsSuccess) {  
  6.                 if (isOnLoad != true) {  
  7.                     setTimeout(function () { getMultiProcessStatus(); }, 1000);  
  8.                 }  
  9.                 return;  
  10.             }  
  11.             if (response.Data != null) {  
  12.                 if (!response.Data.IsCompleted) {  
  13.                     setTimeout(function () { getMultiProcessStatus(); }, 1000);  
  14.                 }  
  15.                 else {  
  16.                     if (isOperationInprogress) {  
  17.                         var msg = "Total Records : ";  
  18.                         msg += response.Data.TotalRecords;  
  19.                         msg += ",<br>";  
  20.                         msg += "Total Success : ";  
  21.                         msg += response.Data.SuccessRecords;  
  22.                         msg += ",<br>";  
  23.                         msg += "Total fail : ";  
  24.                         msg += response.Data.FailedRecords;  
  25.                         showSnackbar(msg);  
  26.                     }  
  27.                 }  
  28.                 isOperationInprogress = false;  
  29.                 if (response.Data.IsCompleted && isOnLoad == true) {  
  30.   
  31.                 }  
  32.                 else  
  33.                     updateProgress(parseInt(response.Data.Percentage));  
  34.             }  
  35.         });  
  36. }  

The above method is to get the status of progress of our task. This will be called on the page load of our page’s JavaScript file. In the above method, you can see that while creating the object, we have used the same variables which we declared in _layout.cshtml, so in our real world application, we will set those variables according to the logged in user and the module of the current user.

  1. function blockCurrentProcessing() {  
  2.     isOperationInprogress = true;  
  3.     showMultiThreadingProgressBar();  
  4.     getMultiProcessStatus();  
  5. }  

The above method is to block any other heavy operation. Here isOperationInProgress is a global variable in site.js. So now, in our index JavaScript file, we will add the following code.

  1. getMultiProcessStatus(true);  

We will write the above line on page load.

  1. $('#btnStart').click(function () {  
  2.         if (isOperationInprogress) {  
  3.             alert('process already going on');  
  4.             return;  
  5.         }  
  6.         makeAjaxCall(url).  
  7.             done(function (response) {  
  8.                 if (response.isSuccess) {  
  9.                     showSnackbar(response.returnMessage);  
  10.                     blockCurrentProcessing();  
  11.                 }  
  12.             }).  
  13.             fail(function (error) { });  
  14.     });  

Then, we will write the button click of start button. So, our code is complete. Now, we will run it.

 

ASP.NET Core
ASP.NET Core
ASP.NET Core
As you can see, we have a working multithreading application with updates shown in real time. If you wish to see/download the code, please click here!

Summary

In today’s article, we have seen how to implement multithreading in ASP.NET Core 2.0 MVC application and show its updates in real-time in our web application.

Hope you all liked it.

Happy Coding!