Sunday, 16 November 2014

Parallel processing with C# - Working with Task factory

While we were working on performance improvement of our project, we found that one foreach loop is taking long time to execute and hence, decreasing the performance rapidly. The code looked like something following - 
 
                    if (!String.IsNullOrWhiteSpace(lstScreenIds))
                    {
                        // Create business class object
                        BLVisitList objVisitList = new BLVisitList();
 
                        // Loop through each Screen and call DoPrevInfo
                        var screenIds = lstScreenIds.Split(',').ToList();
                        
                        foreach (var screenId in screenIds)
                        {
                            objVisitList.DoPrevInfo(ActualUserId, PatientId, ActualRoleId, visitId, screenId, IpAddress);
                        }
                    }
 
Here, lstScreenIds is a comma-separated list of some screen-ids and DoPrevInfo was supposed to be called for each screen id in the list.
 
DoPrevInfo is doing some big Inserts and Updates in DB and hence, if the number of screen ids is more (generally we had some 20-25 screen ids), it was taking huge time to execute and complete the operation. It was taking almost 50 seconds to complete the operation
 
This 50 seconds is the sum of the times taken by the method for all the screen ids. Say, for screenId 'A', the method takes 10 seconds to execute, for 'B' it takes 15 seconds and for 'C' it takes 25 seconds - as the method is called for one screenId only after it completes the execution for the previous one (in serial way), so, the total time taken is (10 + 15 + 25) = 50 seconds.
 
To fix this, we used parallel processing approach as I am describing below. We changed the code to use tasks - 
 
 
                    if (!String.IsNullOrWhiteSpace(lstScreenIds))
                    {
                        BLVisitList objVisitList = new BLVisitList();
 
                        //Loop through each Screen and Call the DoPrevInfo Function
                        var screenIds = lstScreenIds.Split(',').ToList();
 
                        // This line creates a list of tasks that we will be using in future
                        List<Task> tasks = new List<Task>();
 
                        // Saving the current context
                        var current = HttpContext.Current;
 
                        foreach (var screenId in screenIds)
                        {
                            // Starting a new task for each screenId to make it execute parallely
                            tasks.Add(Task.Factory.StartNew(() =>
                            {
                                // Setting current context
                                HttpContext.Current = current;
 
                                // Call the method
                                objVisitList.DoPrevInfo(ActualUserId, PatientId, ActualRoleId, visitId, screenId, IpAddress);
                            }));
 
                        }
                        
                        // Wait until all the tasks are over, then leave the code block.
                        Task.WaitAll(tasks.ToArray());
                    }
 
With this change, we made the foreach loop to use separate tasks for separate screen id and go on parallel way - at the same time. So, the time taken now is the longest time that is taken by the method for individual screenIds.
 
To explain, from our above example, now the complete process will take 25 seconds (that is the maximum time among 'A'/'B'/'C' the method takes to complete execution) - given that, the execution for the other screen ids (10 seconds and 15 seconds respectively) will be completed within that maximum time span!
 
This approach made our code to execute only in 9 seconds from 50 seconds (Obviously that is not 'only' if you are concerned on performance, I am saying from the old time-taken reference :) ) without touching/changing/optimizing the DoPrevInfo method or any DB query/stroed-procedure - This is only due to this tasking.
 
Please refer to the comments to understand what the code is doing. I will still explain some code and why we needed these.
 
1. Inside DoPrevInfo method, we were accessing HttpContext.Current object, but after the task implementation, we found that we are not getting the HttpContext.Current object value being inside tasks. So, there we need to set the HttpContext.Current object value everytime we enter a task.
 
var current = HttpContext.Current; - This line fetches the Current object value being outside the task and after that, we are setting the value by 
HttpContext.Current = current; inside the task. This works fine :)
 
2. Task.WaitAll(tasks.ToArray()); - This line makes the program control to wait for all the tasks to complete before leaving the code block - Otherwise, the control leaves the code block after the first task is completed.

StartDate-EndDate validation with Kendo datepicker and MVC

 If we have two DatePicker controls for StartDate and EndDate and we are writing long validation methods to check if user has given start date greater than end date etc., we can try something else - We can restrict user to select wrong dates.
 
To explain, if user has selected StartDate as 1st Jan 2014, then the EndDate control should populate dates starting from 1st Jan 2014 and not the previous dates.
 
On the other hand, if user selects EndDate first as say 10th Jan 2014, then StartDate should have a maximum selectable date as 10th Jan 2014 and not the dates after that. 
 
Restricting user to select wrong dates at the time of input is obviously better than checking after user clicks a button and expects some output. This improves user experience.
 
This is how we can implement this using KendoUI and MVC - 
 
<label for="start"> Start Date </label>
@(Html.Kendo().DatePicker()
    .Name("start")
    .Events(e => e.Change("StartDateChanged"))
)

<label for="end"> End Date </label>
@(Html.Kendo().DatePicker()
    .Name("end")
    .Events(e => e.Change("EndDateChanged"))
)

<script type="text/javascript">
    
    function StartDateChanged() {
        var startDate = this.value();
        var endPicker = $("#end").data("kendoDatePicker");
        
        if (startDate) {
            startDate = new Date(startDate);
            startDate.setDate(startDate.getDate());
            endPicker.min(startDate);
        }
        else {
                
        }
    }

    function EndDateChanged() {
        var endDate = this.value();
        var startPicker = $("#start").data("kendoDatePicker");

        if (endDate) {
            endDate = new Date(endDate);
            endDate.setDate(endDate.getDate());
            startPicker.max(endDate);
        }
    }
</script>
 
It will show only the valid Dates to user and user can select from there only.

Adding custom button to Kendo editor control

We can customize Kendo editor control with our own custom buttons. Like, say, in any place we are using the kendo editor control and we need to use the horizontal line separation between two Paragraphs. In that case, we will be using the horizontal line very frequently and this is better to add a custom button for drawing the horizontal line.
 
Here is an example how we can one custom button to the Kendo editor control - 
 
@(Html.Kendo().Editor()
    .Name("editor")
      .Tools(tool => tool.CustomButton(cb => cb.Name("custom")
                                               .ToolTip("Draw horizontal line")
                                               .Exec(@<text>
                                                          function() {
                                                            var editor = $(this).data("kendoEditor");
                                                            editor.exec("inserthtml", { value : "<hr />" })
                                                          } 
                                                    </text>))
        )
)
 
The custom button will be displayed in the tool box and we can simply click that to get our work done quickly.
 
We can add tooltip as well to the button. Like, in the upper example, the button will show a tooltip - "Draw horizontal line" to express what the button does.