并行执行存储过程

10

我有这两个方法

public DataTable GetData1(int Id)
{
    DataTable dt = new DataTable();

    using (SqlConnection sqlcon = new SqlConnection(database.Connection.ConnectionString))
    {
        using (SqlCommand cmd = new SqlCommand("spGetData1", sqlcon))
        {
            cmd.CommandType = CommandType.StoredProcedure;

            cmd.Parameters.Add(new SqlParameter() { ParameterName = "@id", Value = Id});

            using (SqlDataAdapter da = new SqlDataAdapter(cmd))
            {
                da.Fill(dt);
            }
        }
    }

    return dt;
}

public DataTable GetData2(int Id)
{
    DataTable dt = new DataTable();

    using (SqlConnection sqlcon = new SqlConnection(database.Connection.ConnectionString))
    {
        using (SqlCommand cmd = new SqlCommand("spGetData2", sqlcon))
        {
            cmd.CommandType = CommandType.StoredProcedure;

            cmd.Parameters.Add(new SqlParameter() { ParameterName = "@id", Value = Id});

            using (SqlDataAdapter da = new SqlDataAdapter(cmd))
            {
                da.Fill(dt);
            }
        }
    }
    return dt;
}

我希望一次性执行它们并获取数据以进一步处理。

我尝试了类似于以下的方法:

var task1 = Task.Factory.StartNew(() => database.Data.GetData1(1));
var task2 = Task.Factory.StartNew(() => database.Data.GetData2(2));

var taskList = new List<Task> { task1, task2 };

Task.WaitAll(taskList.ToArray());

但是在最后一行出现了崩溃

有一个或多个错误。

内部异常为

对象引用未设置为对象的实例。

堆栈跟踪

在 System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken) 中

connectionString 是从 System.Data.Entity.DbContext.Database 类获取的。

 public class DatabaseRepository : IDisposable
    {
        DbContext dbContext;

        public DatabaseRepository()
        {
             dbContext = new DbContext("connection string ...");
             Data = new DataRepository(dbContext.Database);

        }
        public DataRepository Data { get; set; }
}

在此输入图片描述

但即使我手动设置连接字符串,错误仍然相同,所以我不认为错误出在这里。

   using (SqlConnection sqlcon = new SqlConnection("connection string ..."))
   {
      using (SqlCommand cmd = new SqlCommand("spGetData2", sqlcon))
      {
       ...
      }
   }

我该怎么做?我看到一些示例使用Async返回类型,但我不想复制这些方法。


4
由于AggregateException只是一个包装器,没有实际的错误信息,所以您应该调查引发异常的InnerException - VMAtm
1
@Cameron,他正在使用SqlDataAdapter,它会自动打开连接。 - Kirill Shlenskiy
1
堆栈跟踪是什么? - VMAtm
1
@Muflix 我只看到一个可能的候选者:database.Connection - VMAtm
1
很难回答,因为只有数据库(或者如果数据库已被释放,则是database.Connection)可以在这里为空,并且您没有展示关于“database”变量的任何信息。请展示完整的类定义。 - Evk
显示剩余8条评论
2个回答

5
database.Connection.ConnectionString 是一个静态字符串,否则你无法编译,因为会出现“非静态字段、方法或属性的对象引用”的错误。请注意,这并不是 Connection String 没有初始化,因为它是静态的……即使你故意将静态字符串初始化为 Null,错误消息也会是:“InnerException = {“ConnectionString 属性尚未初始化。”}”。以下是一个重现示例,只有当你的 GetData 方法在空对象中时才会出现此错误:
namespace database
{
public class Program
{
    static void Main(string[] args)
    {
        //WORKS!!
        var repro = new database.Data();
        var task1 = Task.Factory.StartNew(() => repro.GetData1(3));
        var task2 = Task.Factory.StartNew(() => repro.GetData2(5));
        var taskList = new List<Task> { task1, task2 };
        Task.WaitAll(taskList.ToArray());

        //FAILS WITH ERROR REPORTED!!
        repro = null;
        var task1 = Task.Factory.StartNew(() => repro.GetData1(3));
        var task2 = Task.Factory.StartNew(() => repro.GetData2(5));
        var taskList = new List<Task> { task1, task2 };
        Task.WaitAll(taskList.ToArray());
    }
}

class Data
{
    private string connectionString = "Server=.;Database=CRUD_Sample;Integrated Security=True;Asynchronous Processing = True;";
    public DataTable GetData1(int Id)
    {
        DataTable dt = new DataTable();
        using (SqlConnection sqlcon = new SqlConnection(connectionString))
        {
            using (SqlCommand cmd = new SqlCommand("Get_CustomerbyID", sqlcon))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.Add(new SqlParameter() { ParameterName = "@id", Value = Id });
                using (SqlDataAdapter da = new SqlDataAdapter(cmd))
                {
                    da.Fill(dt);
                }
            }
        }
        return dt;
    }

    public DataTable GetData2(int Id)
    {
        DataTable dt = new DataTable();
        using (SqlConnection sqlcon = new SqlConnection(connectionString))
        {
            using (SqlCommand cmd = new SqlCommand("Get_CustomerbyID", sqlcon))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.Add(new SqlParameter() { ParameterName = "@id", Value = Id });
                using (SqlDataAdapter da = new SqlDataAdapter(cmd))
                {
                    da.Fill(dt);
                }
            }
        }
        return dt;
    }
}
}

调试

如何找到空引用异常的源头?除了查看异常本身 - 关键是NRE将在发生它的位置抛出,然后您将鼠标悬停在代码行上的变量上,查看哪个对象为空。


谢谢,这个在单独的控制台项目中对我有用,但是我无法将此解决方案复制到我正在工作的项目中。但是我还没有看到任何区别 :/ 我尝试重新初始化reporsitory DataRepository data = new DataRepository(new System.Data.Entity.DbContext(@"connection string.").Database); 并运行 data.GetData1(1) 但它在 Task.WaitAll(taskList.ToArray()); 行崩溃了.. 如果我不使用 Task 块执行它,它可以正常工作。 - Muflix
@Muflix,你试过Jeremy的调试建议了吗?结果如何?哪个对象是null? - SergGr
GetData() 方法中设置断点,当 Task.WaitAll(taskList.ToArray()); 调用触发/执行时,代码控制(黄色调试行)将停止。然后通过每个方法(在 Debug>Threads 窗口中冻结一个线程以便于调试)进行步进,然后您将能够看到 database.Data 对象为空。我猜它为空的原因是配置问题,请运行一个使用 EF 的简单示例并找出差异所在。 - Jeremy Thompson
1
是的,我终于找到错误了!问题就在于在该项目中,对于所有的过程都将“@user”参数设置为强制参数,但当以异步方式触发时,它使用的是“HttpContext.Current.User.Identity.Name”,而这是为空的。 - Muflix

1

更新:

Task.WaitAll会导致当前线程阻塞,直到所有任务完成。使用Task.WhenAll以避免在等待任务完成时占用其他线程。

var task1 = Task.Factory.StartNew(() => database.Data.GetData1(1));
var task2 = Task.Factory.StartNew(() => database.Data.GetData2(2));

var taskList = new List<Task> { task1, task2 };

await Task.WhenAll(taskList.ToArray());

var result1 = await task1;
var result2 = await task2;

原始答案(仍然适用)。

根据评论中提供的额外信息,我对封装代码的类做出了一些假设。在并行执行时,可能会导致database.Connection超出范围,从而导致NRE。在对象的生命周期早期提取连接字符串,并在获取数据时重复使用它。

 public class MyDataClass {

    string connectionString;
    private Database database;

    public MyDataClass(DbContext context) {
        this.database = context.Database;
        connectionString = database.Connection.ConnectionString;
    }

    public DataTable GetData1(int Id) {
        var dt = new DataTable();
        using (var sqlcon = new SqlConnection(connectionString)) {
            using (var cmd = new SqlCommand("spGetData1", sqlcon)) {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.Add(new SqlParameter() { ParameterName = "@id", Value = Id });

                using (var da = new SqlDataAdapter(cmd)) {
                    da.Fill(dt);
                }
            }
        }

        return dt;
    }

    public DataTable GetData2(int Id) {
        var dt = new DataTable();
        using (var sqlcon = new SqlConnection(connectionString)) {
            using (var cmd = new SqlCommand("spGetData2", sqlcon)) {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.Add(new SqlParameter() { ParameterName = "@id", Value = Id });

                using (var da = new SqlDataAdapter(cmd)) {
                    da.Fill(dt);
                }
            }
        }
        return dt;
    }
}

谢谢您的回答,我尝试了但是错误还是一样的,我已经在我的问题中更新了这种情况。 - Muflix
@Muflix,使用WhenAll是否有区别?我认为这可能是问题的根源。使用等待所有会导致其他线程阻塞。 - Nkosi
我将代码更改为 Task.WhenAll(taskList.ToArray());,它没有抛出任何错误,但在数据库分析器中我看不到任何数据库访问。当我将代码更改为 await Task.WhenAll(taskList.ToArray()); 时,会出现以下错误 The await operator can only be used within an async method. Consider marking this method with the async modifier and changing its return type to Task<ActionResult>,因为我的存储库过程不返回 Task 对象。 - Muflix
@Muflix展示封装OP代码的代码。这条消息很有意义。这是异步和同步代码混合的情况。 - Nkosi

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