Blog Home  Home Feed your aggregator (RSS 2.0)  
Mayur's Blog - An asynchronous module or handler completed while an asynchronous operation was still pending
 
# Sunday, April 10, 2016

There are many useful posts available to resolve this issue. This issue arises due to race condition. It is important that ASYC methods are written carefully. Care should be taken so that all the references within ASYC method should not return VOID but at least a TASK.

I came across a scenario where a method responsible to execute database and smtp operations was running into this problem. As shown below the database operations were using ASYC methods, however, SMTP was a non-async method. It was a VOID method with no return

Database operations were running into AsyncLock to make sure that it is thread safe. Thread safety was needed as unique identifier of a company record was generated by the code.

After database operations are successful, ProcessRegistration calls a method to run an smtp operation. As it is not an ASYC method and does not return, ProcessNewRegistration Task can declare itself completed. However, smtp operation may not be complete as emailing sometimes not quicker depending on how many emails in the queue of SMTP Server. In this case, when the thread running ProcessNewRegistration Task tries to complete the task, it runs into a problem. This is due to pending task of smtp operation might be still not complete.

The resolution of this problem is to have SendRegistrationEmail as ASYNC too. This allows to await this method. Doing this forces the thread to wait before issuing a token to complete ProcessNewRegistration task. 

If you are running such problems, a good examination of the code base is needed. Refactoring may be needed. The rule of thumb is never use VOID methods.

public async Task<Company> ProcessNewRegistration(Company company, decimal registrationCost, string paymentId)
        {
            var login = await loginRepositoryService.GenerateNewLogin(Interfaces.Enum.UserRole.ClientAdmin);
            company = await GenerateCompany(company, login);
            SendRegistrationEmail(company.CompanyCode, company.Contact1Name, company.Contact1Email, registrationCost, paymentId);
            return company;
        }
private void SendRegistrationEmail(string companyCode, string name, string email, decimal amount, string paymentId)
        {
            var body = $@"<strong>Hi {name}</strong>
                        <p>Thank you very much for your registration.</p>
                        <p>Please accept this email as your reciept of payment.</p>
                        <p>Registration details</p>
                        <strong>Company code: </strong>{companyCode}<br />
                        <strong>Amount paid: </strong>${amount}<br />
                        <strong>Pyament id: </strong>${paymentId}<br />
                        <strong>Date of payment: </strong>{DateTime.Now.ToString("yyyy-MM-dd")}<br />
                        <p>We appreciate your business and looking forward to exceed your expectations.</p>
                        <p>&nbsp;</p>
                        <strong>Sincerely,</strong><br />
                        Customer Service Team
                        ";
            UtilityService.SendEmail(email, "admin@mail.ca", "Confirmation: Registration and Payment", "admin@mail.ca", string.Empty, body, null, null);           
        }
 
private async Task<Company> GenerateCompany(Company company, Login login)
        {            
            using (var al = await asyncLocker.LockAsync())
            {
                var latestCompanyId = await companyService.MaxAsync(c => c.CompanyId);
                var latestCompany = await companyService.FindAsync(latestCompanyId);
                var code = 0;
                if (!int.TryParse(latestCompany.CompanyCode, out code))
                {
                    throw new Exception("Error while generating company code");
                }
                company.CompanyCode = (++code).ToString();
                company.Logins.Add(login);
                companyService.Add(company);
                var id = await companyService.FlushChanges(company);                
            }

            return company;            
        }

Corrected methods to run smtp operations.

public async Task<Company> ProcessNewRegistration(Company company, decimal registrationCost, string paymentId)
        {
            var login = await loginRepositoryService.GenerateNewLogin(Interfaces.Enum.UserRole.ClientAdmin);
            company = await GenerateCompany(company, login);
            await SendRegistrationEmail(company.CompanyCode, company.Contact1Name, company.Contact1Email, registrationCost, paymentId);
            return company;
        }
private async Task SendRegistrationEmail(string companyCode, string name, string email, decimal amount, string paymentId)
        {
            var body = $@"<strong>Hi {name}</strong>
                        <p>Thank you very much for your registration.</p>
                        <p>Please accept this email as your reciept of payment.</p>
                        <p>Registration details</p>
                        <strong>Company code: </strong>{companyCode}<br />
                        <strong>Amount paid: </strong>${amount}<br />
                        <strong>Pyament id: </strong>${paymentId}<br />
                        <strong>Date of payment: </strong>{DateTime.Now.ToString("yyyy-MM-dd")}<br />
                        <p>We appreciate your business and looking forward to exceed your expectations.</p>
                        <p>&nbsp;</p>
                        <strong>Sincerely,</strong><br />
                        Customer Service Team
                        ";
            await UtilityService.SendEmail(email, "admin@mail.ca", "Confirmation: Registration and Payment", "admin@mail.ca", string.Empty, body, null, null);           
        }
 
Sunday, April 10, 2016 11:48:03 PM UTC  #       | 
Copyright © 2022 Mayur Bharodia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.
Pick a theme: