This is a tough one. Using the exact same query string, the exact same following code:
using (var db = new SqlConnection(queryString))
{
await db.OpenAsync();
var results = await db.ExecuteSomethingAsync...;
db.Close();
{
Will work when ran from a windows application. It will however get stuck forever in await OpenAsync()
when ran from IIS Express or IIS 7. If I replace that line with db.Open()
it works though. Any suggestions?
As others have mentioned, first ensure you have no Wait
or Result
calls up your hierarchy. A chain of async
methods ends at an entry point that depends on the framework. In a UI/WebForms app, this is usually an async void
event handler. In a WebAPI/MVC app, this is usually an async
action.
Two other things to check for ASP.NET are:
- Ensure your target platform is .NET 4.5.
- Ensure you have
UseTaskFriendlySynchronizationContext
set to true
.
If you need to support async
in a shared library on multiple platforms, you may find the Microsoft.Bcl.Async
NuGet library to be helpful.
await
needs to be treated with caution in ASP.NET, because of how the sync-context wants to serialize the work for a single request. The code you post is probably fine by itself - the await
isn't going to block. However, I expect that somewhere in the call chain for this, you are calling .Wait()
or accessing .Result
, rather than await
.
There are a few options here:
- don't use
.Wait()
or .Result
(or similar) at all - instead, only use await
and make it a properly async action
or, use .ConfigureAwait(false)
to tell it to ignore sync-context; unfortunately, this would need to be added to all the places that you await
, i.e.
await db.OpenAsync().ConfigureAwait(false);
var results = await db.ExecuteSomethingAsync(...).ConfigureAwait(false);
or, just use sync code - in most cases the sql is going to run very quickly - so pushing it async is not necessarily helping as much as you might think; obviously this may vary between usage; but a key point to keep in mind here is that ASP.NET is already inherently threaded - it isn't as though the entire server grinds to a halt here
I noticed another important point to keep in mind when writing async/await code in ASP.Net Web Forms code-behind, and that is to make sure Async="true" in the page declaration. This attribute is false by default.
If you do not do this then you could see your page in a forever-loading-state.
<%@ Page Language="C#" Async="true" %>
Also, in .Net 4.5, according to the answer by Stephen we need to have 'UseTaskFriendlySynchronizationContext' set to true in web config appsettings section. Another useful appsetting is AllowAsyncDuringSyncStages which needs to be false for async/await code in Webforms code-behind. These settings are both false by default.
<add key="aspnet:AllowAsyncDuringSyncStages" value="false" />
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
I had the following sample async/await code in an ASP.Net WebForm that ran very quickly using the above settings and using await all the way through as suggested by Stephen. If these recommendations are not followed then you could see the Webforms page loading forever in the browser.
protected async void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string sql = @"DELETE FROM dbo.Table1
WHERE Processed = 1";
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["MainDB"].ConnectionString);
SqlCommand cmd = new SqlCommand(sql, conn);
int numberOfRecordsUpdated = await UpdateDatabaseAsync(conn, cmd);
}
}
public async Task<int> UpdateDatabaseAsync(SqlConnection conn, SqlCommand cmd)
{
int i = 0;
try
{
await conn.OpenAsync();
i = await cmd.ExecuteNonQueryAsync();
}
catch (Exception ex)
{
//log the error
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
finally
{
if (conn != null)
{
conn.Close();
conn.Dispose();
}
if (cmd != null)
{
cmd.Dispose();
}
}
return i;
}