Working with async/await in .Net 4.8

await waits for the completion of an asynchronous operation and directly catches any errors that occur during this process. This ensures that you don’t overlook exceptions.
If you only assign the task to a variable and do not await it e.g., _ = DoSomethingAsync()Exceptions from the operation may go unobserved, potentially resulting in an UnobservedTaskException.
To make your code more robust and resilient to errors, it is recommended to use await. Even in fire-and-forget scenarios, logging background exceptions is important for debugging and stability.
Where Should You Place the try/catch Block in ASP.NET Web Forms?
When working with asynchronous methods in ASP.NET Web Forms, particularly when using Page.RegisterAsyncTask, the safest and most reliable approach is to place your try/catch block inside the async method itself — not where it is registered or called.
protected void btnSMS_Click(object sender, EventArgs e)
{
Page.RegisterAsyncTask(new PageAsyncTask(SendSMSHandler));
}
private async Task SendSMSHandler()
{
try
{
await GridSMS();
}
catch (Exception ex)
{
await PublicFunctions.SendMailAsync();
}
}
Here’s why this structure is important:
The
RegisterAsyncTaskmethod only schedules the async task to be executed later in the page lifecycle.If an exception occurs inside
SendSMSHandlerand there's notry/catchwithin that method, it won’t be caught at the calling level.Instead, the unhandled exception bubbles up and is caught by ASP.NET's internal error handler, resulting in a
System.Web.HttpUnhandledException.
This can cause:
A full-page crash or white screen
Application pool instability
In some cases, process-level crashes if unmanaged code is involved
Common Mistake – Catching Outside the Async Method
This structure is not effective:
protected void btnSMS_Click(object sender, EventArgs e)
{
try
{
Page.RegisterAsyncTask(new PageAsyncTask(SendSMSHandler));
}
catch (Exception ex)
{
// ❌ This will not catch exceptions inside SendSMSHandler
}
}
Why? Because RegisterAsyncTask doesn’t execute the method immediately — it only queues it.
Why should we use the async method?
The await keyword provides the feature that allows other processes to continue asynchronously while waiting for the task to finish its job, that is, non-blocking.
For example, when you make an asynchronous web service request, ASP.NET will not use threads between the async method call and the await. It prevents thread starvation.
Authoring Async Methods
async Task MyMethod() { }
which creates a method that can be awaited, but does not return any value,async Task<T> MyReturningMethod { return default(T); }
which creates a method that can be awaited, and returns a value of the type T,protected void DetailsView1_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e) { }
async void methods are only suited for UI event handlers
Do NOT use things like Task.Run directly instead of RegisterAsyncTask.
Because doing so takes the execution out of ASP.NET’s SynchronizationContext, which can cause issues with UI updates.
Question: Must there be an await inside an async Task method?
Answer: If there's no await, then why are you using async?
If you're not using await inside a method, there's no point in marking it async. Because: an async method only provides asynchronous behavior when await is used.
So if there's no await, don't make the method async.
In Web Forms applications, async implementation appears in two forms:
When the method is declared in the HTML (e.g.,
OnRowCommand="grdOrders_RowCommand"),When the method is not referenced in markup and triggered independently.
Let’s go through both:
1. If the event is declared in HTML (e.g., OnRowCommand="grdOrders_RowCommand"):
ASP.NET Web Forms does not support async Task event handlers in this case.
You must use void.
Method A (Recommended):
Preserves ASP.NET's page lifecycle.
Ensures the page does not render until the async operation completes.
Provides a safer and more manageable structure.
However, be aware of NullReferenceException when accessing controls like TextBoxes.
This is due to timing issues between the UI and async code execution.
Therefore, if you need to access values from controls (e.g., username.Text),
make sure you retrieve them before the async method is invoked.
Root cause:
Code executed via RegisterAsyncTask runs after standard page events have completed
(typically around PreRenderComplete).
protected void DetailsView1_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)
{
var detailsView = (DetailsView)sender;
var isNETGSM = ((CheckBox)detailsView.FindControl("chkNETGSM"))?.Checked ?? false;
if (isNETGSM)
{
var company = ((DropDownList)detailsView.FindControl("DropDownList1"))?.SelectedValue;
var username = ((TextBox)detailsView.FindControl("txtUsername"))?.Text;
var password = ((TextBox)detailsView.FindControl("txtPassword"))?.Text;
Page.RegisterAsyncTask(new PageAsyncTask(async () =>
{
await UpdateSMSheaders(company, username, password);
}));
}
}
Method B (Not Recommended):
If exceptions are not properly handled with try-catch, the app may crash.
Managing flow within the page lifecycle is harder and riskier.
protected async void DetailsView1_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)
{
await UpdateSMSheaders(sender);
}
2. If the method is not referenced in the HTML markup,
you can safely use Page.RegisterAsyncTask.
Examples:
protected void Page_Load(object sender, EventArgs e)
{
Page.RegisterAsyncTask(new PageAsyncTask(MembershipHandler));
}
protected void btnUye_Click(object sender, EventArgs e)
{
Page.RegisterAsyncTask(new PageAsyncTask(MembershipHandler));
}
private async Task MembershipHandler()
{
await PublicFunctions.SendMailAsync();
}
ConfigureAwait
In terms of ConfigureAwait, the default value is ConfigureAwait(true). Therefore, if you don’t explicitly specify it, your code will behave as if you used ConfigureAwait(true).
ConfigureAwait(false), I recommend using it for library code and not application code. If you don't write any library code then you probably won't ever have to use it.
You make simple use of await, and the right things happen with regards to callbacks/continuations being posted back to the original context if one existed. This leads to the general guidance:
The setting of downloadBtn.Content = text needs to be done back in the original context.
Right usage :
private static readonly HttpClient s_httpClient = new HttpClient();
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
downloadBtn.Content = text;
}
Wrong usage :
private static readonly HttpClient s_httpClient = new HttpClient();
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime").ConfigureAwait(false); // bug
downloadBtn.Content = text;
}
The same would go for code in a classic ASP.NET app reliant on HttpContext.Current; using ConfigureAwait(false) and then trying to use HttpContext.Current is likely going to result in problems.
Reference :
https://devblogs.microsoft.com/dotnet/configureawait-faq/
https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html
WaitAll vs. WhenAll What's the difference?
Task.WaitAll() then the UI thread is blocked and the UI is never updated. If you use await Task.WhenAll() then the UI thread is not blocked, and the UI will be updated.
So;
WaitAll is a blocking call
When all is not. Code will continue executing
Use which when:
WaitAll when cannot continue without having the result
WhenAll when what just to be notified, not blocked
References :
https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/https://stackoverflow.com/questions/38423472/what-is-the-difference-between-task-run-and-task-factory-startnewhttps://blog.stephencleary.com/2013/08/startnew-is-dangerous.html
https://www.linkedin.com/pulse/should-i-use-configureawaittrue-configureawaitfalse-viswanathan/




