什么时候过多"异步和等待?所有方法都应该返回Task吗?

时间:2015-06-05 08:04:23

标签: c# asp.net .net asynchronous async-await

我正在构建一个项目并使用async和await方法。每个人都说异步应用程序是从头开始构建的,那么你真的应该有任何同步方法吗?是否所有方法都返回一个Task,以便您可以异步使用?

让我们举一个简单的例子,我使用Sql将数据加载到集合中,这里有一些代码。

此代码使用ExecuteQueryAsync方法从表中加载数据,方法GetQuery构造SQL,但调用GetTableColumns。生成并执行SQL后,我遍历集合并通过调用GetDataFromReader填充每个对象。

我的非异步方法应该是异步吗?我是在想太多的同步编程方式而忽略了什么?

public async Task<ICollection<MyObject>> ExecuteQueryAsync(Module module, List<SqlParameter> parameters)
{
    var result = new Collection<MyObject>();
    var query = GetQuery(module);

    using (var conn = new SqlConnection(_context.Database.Connection.ConnectionString))
    {
        await conn.OpenAsync();

        using (var cmd = new SqlCommand(query, conn))
        {
            if (parameters != null)
                cmd.Parameters.AddRange(parameters.ToArray());

            using (var dr = await cmd.ExecuteReaderAsync())
            {
                while (await dr.ReadAsync())
                {
                    result.Add(GetDataFromReader(module, dr));
                }

            }
        }

    }

    return result;
}

public string GetQuery(Module module)
{
    return "SELECT " + string.Join(",", GetTableColumns(module).ToArray()) + " FROM [TableA] ";
}

public List<string> GetTableColumns(Module module) 
{
    var columnNames = new List<string>();

    // get all list fields for the module
    var fields = (from a in module.Groups.SelectMany(a => a.Fields) select a).ToList();

    foreach (var field in fields)
    {
        if (field.Type == FieldType.List) {
            string query = "STUFF(";
            query += "(SELECT ';' + [Value] FROM [TableB] FOR XML PATH(''))";
            query += ", 1, 1, '') AS [" + field.ColumnName + "]";

            columnNames.Add(query);
        } else {
            columnNames.Add("[" + field.ColumnName + "]");
        }
    }

    return columnNames;
}

public MyObject GetDataFromReader(Module module, IDataReader dataReader)
{
    var entity = new MyObject();

    for (var i = 0; i < dataReader.FieldCount; i++)
    {
        object value = null;
        var fieldName = dataReader.GetName(i);

        if (!dataReader.IsDBNull(i))
        {
            value = dataReader.GetValue(i);
        }

        entity[fieldName] = value;
    }

    return entity;
}

2 个答案:

答案 0 :(得分:11)

所有异步”背后的理念是促进non-blocking I/O

也就是说,您的主要异步代码可能会让环境优先考虑您的应用程序或服务的执行方式,并在多线程,多进程系统中实现尽可能多的并行执行。

例如,ASP.NET Web API,ASP.NET MVC甚至ASP.NET Web窗体(代码隐藏)都可以利用 all async 继续向其他用户提供Web请求正在执行一些异步操作。因此,即使像IIS或Katana这样的Web服务器可能限制并发请求的数量,异步操作也会在与请求线程不同的线程中执行,这允许Web服务器在异步操作获得结果时响应其他请求他们需要继续:

// While WhateverAsync is being executed, current thread can be used 
// by a new request and so on.
// Obviously, this will work this way if WhateverAsync actually
// does its work in another thread...
await WhateverAsync();

所以......你需要异步实现一切吗?即使您返回Task,也不需要提供异步实现:

public Task WhateverAsync()
{
    // This creates a fake Task object which 
    // simulates a Task that has already ended successfully
    // and without creating a child thread!
    // This, this method is a SYNCHRONOUS implementation unless
    // the whole method doesn't execute asynchronous operations.
    return Task.FromResult(true);
}

我的观点是......

  • ...实现返回任务的所有内容,并在方法标识符的末尾使用Async后缀(WhateverAsyncWhoKnowsAsyncDoStuffAsync ... )...

  • ...除非你能确定整个方法总能执行一个非常简单的事情,这个事情不能长时间阻塞应用程序/服务的线程(长时间可能是几毫秒,现在想象一下代码每当调用某个方法时,它不会阻止主应用程序线程持续100ms,并且你的代码可以在等待 100ms时执行某些操作的优先顺序....)。我会在这里包括字符串操作,简单的算术运算,配置方法......

如果您的代码今天不是异步,您可以将其转换为实际的异步操作,而不会影响整个代码库,因为您只需要更改Task.FromResult<T>(T result)调用以实际返回未完成的Task实例

在一天结束时,您的方法具有异步签名,并且它们的依赖关系并不关心它们是否实际上是异步的,并且这些方法实现决定什么是异步或同步而不是将此责任赋予呼叫者。

答案 1 :(得分:5)

如果一个方法内部没有async个操作,那么使它成为async没有任何好处。您应该只有asyncasync操作的方法(I / O,DB等)。

如果您的应用程序有很多这些I / O方法并且它们遍布您的代码库,那不是一件坏事。但是,不要只在同步方法上添加async个关键字。

在您的特定情况下,ExecuteQueryAsync可以通过使用await cmd.ExecuteReaderAsync()进行异步来获益。 GetTableColumnsGetDataFromReader似乎是CPU密集型方法,它们不适合async-await范例。