任务:当所有连接都关闭时

8

我正在尝试使用Task.WhenAll执行多个SqlDataReaders,但是当等待任务时,我会收到“System.InvalidOperationException:无效操作。连接已关闭”的错误信息。

任务的创建:

        List<Task<SqlDataReader>> _listTasksDataReader = new List<Task<SqlDataReader>>();
        _listTasksDataReader.Add(GetSqlDataReader1(10));
        _listTasksDataReader.Add(GetSqlDataReader2(10));
        SqlDataReader[] _dataReaders = await Task.WhenAll(_listTasksDataReader);

我的"SqlDataReader"方法:

    public Task<SqlDataReader> GetSqlDataReader1(int recordCount)
    {
        using (var sqlCon = new SqlConnection(ConnectionString))
        {
            sqlCon.Open();
            using (var command = new SqlCommand("sp_GetData", sqlCon))
            {
                command.Parameters.Clear();
                command.Parameters.Add(new SqlParameter("@recordCount", recordCount));
                command.CommandType = System.Data.CommandType.StoredProcedure;
                return command.ExecuteReaderAsync();
            }
        }
    }

当执行Task.WhenAll时,数据库连接应该被打开,还是我遗漏了什么?


您有两个连接。您在哪个连接上出错了? 我会在 sqlCon.Open() 上设置断点,看看您的代码是否运行了[两次],实际上已经打开了这些连接。 - Robert Altman
4
一旦您的ExecuteReaderAsync调用返回,您的连接将关闭(因为它声明在 using 语句中)。 您的ExecuteReaderAsync调用在数据被读取之前就已经返回了(毕竟它是一个异步读取器!)。 - RB.
2
连接已关闭,因为您由于return语句而跳出了using块。 您应该查看http://codereview.stackexchange.com/a/22916/54013 - AntonR
罗伯特在第二个(“GetSqlDataReader2”)上。 - AsusT9
1
@AsusT9 你需要同时更改这个方法和调用者。调用者应该提供并控制连接。在关闭连接后,你不能使用读取器。 - Panagiotis Kanavos
3个回答

6
可以将CommandBehavior.CloseConnection传递给ExecuteReaderAsync。这样,连接将保持打开状态,直到返回的数据读取器对象被关闭:请参见MSDN 此处此处。在这种情况下,SqlConnection不需要在using语句中。
像这样:
public Task<SqlDataReader> GetSqlDataReader1(int recordCount)
{
    var sqlCon = new SqlConnection(ConnectionString);
    sqlCon.Open();

    using (var command = new SqlCommand("sp_GetData", sqlCon))
    {
        command.Parameters.Clear();
        command.Parameters.Add(new SqlParameter("@recordCount", recordCount));
        command.CommandType = System.Data.CommandType.StoredProcedure;
        return command.ExecuteReaderAsync(CommandBehavior.CloseConnection);
    }
}

3

我有什么遗漏吗?

您正在尝试获取没有基础连接的SqlDataReader吗? 我认为这不会很好地工作。 当您从读取器中读取数据时会发生什么? 连接已经关闭了。

因此,您可能只需要在关闭连接之前读取实际数据:

public async Task<List<T>> GetData1(int recordCount)
{
    using (var sqlCon = new SqlConnection(ConnectionString))
    {
        sqlCon.Open();
        using (var command = new SqlCommand("sp_GetData", sqlCon))
        {
            command.Parameters.Clear();
            command.Parameters.Add(new SqlParameter("@recordCount", recordCount));
            command.CommandType = System.Data.CommandType.StoredProcedure;

            var result = new List<T>();
            var reader = await command.ExecuteReaderAsync();
            // TODO: use `reader` to populate `result`
            return result;
        }
    }
}

2
更新:我会把这个留在这里,但是我刚想起来你不能组合yieldawait...至少现在还不行。
请记住,即使使用return关键字调用command.ExecuteReaderAsync(),该方法的执行也不会停止。这就是_Async()方法的全部意义。因此,在该函数调用之后,代码退出using块。这会导致在您有机会从DataReader中读取数据之前,连接对象被处理掉。
尝试返回一个Task<IEnumerable<IDataRecord>>
public async Task<IEnumerable<IDataRecord>> GetSqlDataReader1(int recordCount)
{
    using (var sqlCon = new SqlConnection(ConnectionString))
    using (var command = new SqlCommand("sp_GetData", sqlCon))
    {
        command.Parameters.Add("@recordCount", SqlDbType.Int).Value = recordCount;
        command.CommandType = System.Data.CommandType.StoredProcedure;

        sqlCon.Open();               
        var rdr = await command.ExecuteReaderAsync();
        while (rdr.Read())
        {
             yield return rdr;
        }
    }
}

请注意,这种模式有一个“陷阱”。每个yield return使用相同的对象,因此如果你不小心,可能会发生一些奇怪的事情。我建议进一步更改此包括代码,将每个记录中的数据放入rdr对象中的内容放入其自己(强类型)的对象实例中:

public async Task<IEnumerable<SomeObject>> GetSqlDataReader1(int recordCount)
{
    using (var sqlCon = new SqlConnection(ConnectionString))
    using (var command = new SqlCommand("sp_GetData", sqlCon))
    {
        command.Parameters.Add(new SqlParameter("@recordCount", recordCount));
        command.CommandType = System.Data.CommandType.StoredProcedure;

        sqlCon.Open();                
        var rdr = await command.ExecuteReaderAsync();
        while (rdr.Read())
        {
             yield return new SomeObject() {Field1 = rdr[1], Field2 = rdr[2], etc};
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接