Search code examples
.netloopsasynchronoustap

Task based Asynchronous Patterns in Loops


I need to make BuildReports() Asynchronous because this Function can take several minutes to complete depending on the number of reports it has to create. It is called by btnClearAll_Click() that I added ‘Async’ and ‘Await’ too. I’ve added ‘Async’ to BuildReports() and tried numerous placements for ‘Await’ including using on the If-Then-Else’ statement prior to the loop. I also tried ‘Awaitable’ for the datatable (dt) created in reportComponent.CreatePDF() (a SQL Select query). ClearAll() is Synchronous.

So I need some help here, I’ve read numerous articles on this but I missing something. Any guidance will be appreciated.

Private Async Sub btnClearAll_Click(sender As Object, e As System.EventArgs) Handles btnClearAll.Click

    Await BuildReports()

    ClearAll()

End Sub

Private Async Function BuildReports() As Task

    Try
        Dim reportComponent As New ReportComponent(CStr(Session("UserConnectionString")))
        Dim dt As New DataTable

        dt = reportComponent.CreatePDF()

        If dt IsNot Nothing Then
            Dim ScheduleDto As New ScheduleDto()
            Dim scheduleDtoList As New List(Of ScheduleDto)()
            Dim report1 As Telerik.Reporting.Report = Nothing

            For Each row As DataRow In dt.Rows
                For i As Integer = 0 To dt.Rows.Count - 1
                    If row IsNot Nothing Then
                       ScheduleDto.Encounter_code = dt.Rows.Item(i).Item("Encounter_code").ToString
                        ScheduleDto.CaseNumber = dt.Rows.Item(i).Item("CaseNumber").ToString
                        ScheduleDto.Case_Name = dt.Rows.Item(i).Item("Case_Name").ToString
                        ScheduleDto.DOS = dt.Rows.Item(i).Item("DOS").ToString
                        ScheduleDto.Provider = dt.Rows.Item(i).Item("Provider").ToString
                        ScheduleDto.Discipline = dt.Rows.Item(i).Item("Discipline").ToString
                        ScheduleDto.Episode = dt.Rows.Item(i).Item("Episode").ToString
                        ScheduleDto.ReportType = dt.Rows.Item(i).Item("ReportType").ToString
                        ScheduleDto.DocumentName = dt.Rows.Item(i).Item("Document_Name").ToString
                        ScheduleDto.ItemType = dt.Rows.Item(i).Item("ItemType").ToString

                        scheduleDtoList.Add(ScheduleDto)
                        report1 = reportComponent.GetReport(ScheduleDto, ScheduleDto.ReportType, CStr(Session("UserConnectionString")))
                       If (report1 IsNot Nothing) Then
                            Dim pdfPath As String = ""
                            Dim DownloadReport As New DownloadReports()
                       DownloadReport.DownloadReport(report1, ScheduleDto, ScheduleDto.DocumentName, ScheduleDto.ReportType, pdfPath) 
                       reportComponent.FinalizeNow(ScheduleDto.Encounter_code)

                        End If
                    End If
                Next
            Next

        Else : Return
        End If

    Catch ex As SqlException
        Throw New Exception(ex.Message & " : Build_Reports")
    Finally
    End Try


End Function

Solution

  • The compiler should be giving you an error with that code, because BuildReports is marked as Async but is not using Await.

    Note that "asynchronous" does not mean "run on a background thread". Thus, there is no way to "make" synchronous code asynchronous; the code itself is synchronous or asynchronous by nature.

    So, for example, if you had naturally-asynchronous database operations (e.g., using Entity Framework), then you could have a naturally-asynchronous BuildReports. However, the DataTable API is not amenable to asynchrony, so DataTable does not have asynchronous APIs at all. It is firmly stuck in the synchronous world.

    In your particular case, it's probably best to run your existing synchronous code on a background thread, which can be done with Task.Run. You can then consume it asynchronously:

    Private Async Sub btnClearAll_Click(sender As Object, e As System.EventArgs) Handles btnClearAll.Click
        Await Task.Run(Function() BuildReports())
        ClearAll()
    End Sub
    
    Private Sub BuildReports()
    ...