In an asp.net application I want to log to a database as efficiently as possible. I am using basic ADO.NET to write to the logging database and I want to do this async, so here is what I do:
using (var conn = new SqlConnection(_connectionString)) {
using (var cmd = new SqlCommand("INSERT INTO dbo.Logs (TimeStamp,ThreadId,Level,Message,Exception) VALUES (@TimeStamp,@ThreadId,@Level,@Message,@Exception)", conn)) {
cmd.Parameters.AddWithValue("@TimeStamp", DateTime.UtcNow);
cmd.Parameters.AddWithValue("@ThreadId", Thread.CurrentThread.ManagedThreadId);
cmd.Parameters.AddWithValue("@Level", level);
cmd.Parameters.AddWithValue("@Message", msg);
cmd.Parameters.AddWithValue("@Exception", ex == null ? "" : ex.ToString());
conn.Open();
cmd.ExecuteNonQueryAsync();
}
}
Now my question is if I should do an await on the cmd.ExecuteNonQueryAsync() statement or if it is ok to omit the await because I basically just need to do a fire-and-forget.
It is not OK to just fire and forget the async operation - it might fail with an exception, and you nearly certainly want to do something (notify the user/retry/blow up) in that case. (Besides, if someone uses a specific option, an exception in the task will eventually bring down the process).
Also: Who is responsible for closing the connection?
Edit: In order to make the problem of connection closing clearer, I will rewrite the using statements with
try
/finally
(I know it is not the exact same IL, but it is close enough to see where the problem is) - in that case, the code roughly becomes:You can see that
cmd.Dispose()
is called right aftercmd.ExecuteNonQueryAsync()
. How can that work? I see two possibilities:cmd.Dispose()
(by design or accident) does not return untilcmd.ExecuteNonQueryAsync
was able to do its work - meaning that you actually only return fromcmd.Dispose
aftercmd.ExecuteNonQueryAsync
; in that case, the code works, but you do not benefit from await/async;cmd.Dispose()
is executed in a way that preventscmd.ExecuteNonQueryAsync()
from completing; in that case the code does not work;In both cases, the program is wrong - you need either
await
or to callWait()
on the task in order to ensure that it behaves correctly.As others have mentioned, strongly consider a stable, well-tested library such as ELMAH or log4net. If you do decide to roll your own, consider an ETW-based solution, which has much less overhead and can easily include related OS/.NET/IIS/ASP.NET events. I've done logging to a database; it's the kind of thing that sounds like a good idea but you run into logistical issues down the line.
To answer your actual question, it is better to
await
it if you need to respond to errors. If this is truly fire-and-forget, then you can just ignore theTask
result. If your method isasync
, the compiler will warn you if you ignore aTask
; to avoid the warning, you can just assign it to an unused variable:The simplest way is to do a fire-and-forget with Task.Run() on a synchronous operation.
E.g.,
Where LogDatabase() is a synchronous standard ADO.NET to ExecuteNonQuery().