Is it safe in C# to omit await statement when doin

2019-07-03 16:17发布

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.

3条回答
小情绪 Triste *
2楼-- · 2019-07-03 16:45

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:

SqlConnection conn;
try {
    conn = new SqlConnection("connString");
    SqlCommand cmd;
    try {
        cmd = new SqlCommand("INSERT INTO dbo.Logs (TimeStamp,ThreadId,Level,Message,Exception) VALUES (@TimeStamp,@ThreadId,@Level,@Message,@Exception)", conn);
        cmd.AddParameters();
        conn.Open();
        cmd.ExecuteNonQueryAsync();
    } finally {
        if (cmd != null) cmd.Dispose();
 } finally {
    if (conn != null) conn.Close();
 }

You can see that cmd.Dispose() is called right after cmd.ExecuteNonQueryAsync(). How can that work? I see two possibilities:

  1. Either cmd.Dispose() (by design or accident) does not return until cmd.ExecuteNonQueryAsyncwas able to do its work - meaning that you actually only return from cmd.Dispose after cmd.ExecuteNonQueryAsync; in that case, the code works, but you do not benefit from await/async;
  2. Or cmd.Dispose() is executed in a way that prevents cmd.ExecuteNonQueryAsync() from completing; in that case the code does not work;

In both cases, the program is wrong - you need either await or to call Wait() on the task in order to ensure that it behaves correctly.

查看更多
狗以群分
3楼-- · 2019-07-03 16:49

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 the Task result. If your method is async, the compiler will warn you if you ignore a Task; to avoid the warning, you can just assign it to an unused variable:

var _ = cmd.ExecuteNonQueryAsync();
查看更多
放荡不羁爱自由
4楼-- · 2019-07-03 17:01

The simplest way is to do a fire-and-forget with Task.Run() on a synchronous operation.

E.g.,

Task.Run(() =>
{
     LogDatabase();

});

Where LogDatabase() is a synchronous standard ADO.NET to ExecuteNonQuery().

查看更多
登录 后发表回答