在不同的线程连接数据库

5
我有一个WPF应用程序,用户在一些文本框中输入数据库信息。用户单击“连接”后,将从用户输入的内容创建连接字符串并建立连接。我注意到,如果用户输入错误的信息,应用程序将挂起直到连接超时。挂起指用户无法与应用程序的其他部分进行交互。
我的目标是在测试连接字符串时保持应用程序响应。
我认为将此工作流程放在不同的线程上是一个好的解决方案。我的想法是,在线程运行时禁用可能需要数据库连接的任何内容。一旦线程返回(并已确认连接字符串有效),我将重新启用所有内容。否则,保持所有内容禁用。
但是,Thread类没有线程完成时的事件通知(或者至少我不知道有这样的)。我还使用过BackgroundWorker类。这个方法更好。然而,当触发RunWorkerCompletedEventHandler事件且连接字符串无效时,我会收到以下异常:
"The calling thread cannot access this object because a different thread owns it."
这可能是因为完成事件处理程序被触发时连接仍未超时。
有人有什么想法吗?或者我应该不尝试将数据库连接多线程化?
我正在做的代码概述:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    dbTool = new DBTool();
    // Create the connection string
    e.Result = dbTool.connectToDB(); // connectToDB() returns a bool (true if connection established)
}

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // connectToDB() returns a bool (true if connection established)
    if(e.Result == true)  // Trying to read e.Result here throws the exception
    {
        // e.Error and e.Cancel should be checked first
        // However, I would like the thread to finish before
        // this event is fired
    }
    if (e.Error != null)
    {
        Console.WriteLine(e.Error.Message);
    }
}

这是WinForms还是WPF?你都提到了。另外,这个示例代码有多接近真实代码?Console.WriteLine()e.Error都不应该抛出此异常。但更新UI控件可能会引发异常。 - Sean U
@SeanU:我已经更新了我的问题。我的实际代码中有一点缺失。 - Chris Dargis
你应该交换检查这些属性的顺序。如果 RunWorkerCompletedEventArgsError 属性不为 null,访问其 Result 属性将引发异常。 - Sean U
从MSDN:如果操作成功完成并且其结果在DoWork事件处理程序中分配,您可以通过RunWorkerCompletedEventArgs.Result属性访问结果。以及:在访问Result属性之前,始终应检查Error和Cancelled属性的值。如果引发异常或取消操作,则访问Result属性会引发异常。 - cshemby
@SeanU:我理解,但这就是问题所在。我需要线程在检查Result属性之前完成,即在触发bw_RunWorkerCompleted事件之前。 - Chris Dargis
一个 BackgroundWorker 直到完成、被取消或引发异常才会触发 RunWorkerCompleted。您可以通过在 RunWorkerCompleted 处理程序中设置断点,然后查看并行堆栈窗口来验证这一点。 - Sean U
4个回答

3
不要将您的DbConnection对象保存在单个全局变量中并在线程之间共享。
.NET环境会自动池化您的连接并共享它们,因此调用new DbConnection()非常快。
应该将连接字符串保留在全局变量中,但然后在每个线程上根据需要创建连接。
编辑:原始发布者可能实际上想要在测试连接字符串时保持WinForms应用程序响应。在这种情况下,您需要生成一个不同的线程来测试连接。从“连接测试线程”中,您可以按照这种模式更新UI - 如何在C#中从另一个线程更新GUI?
public void TestConnectionThread(String connstr_to_test)
{
    // Notify the user that we're doing our test
    string message = "Testing...";
    lblTestResultMessage.SetPropertyThreadSafe(() => lblTestResultMessage.Text, message);

    try {
        dbTool = new DBTool();
        message = dbTool.connectToDB();

    // If something failed, show a useful debugging message
    } catch (Exception ex) {
        message = ex.ToString();
    }

    // Use a lambda expression to communicate results to the user safely
    lblTestResultMessage.SetPropertyThreadSafe(() => lblTestResultMessage.Text, message);
}

抱歉,我应该明确一下,我没有保留DBConnection对象。我只是测试由用户输入的数据构建的连接字符串。之后该对象被删除(我正在使用连接池)。 - Chris Dargis
哦,我想我明白了。这是否意味着您正在尝试测试连接字符串,同时保持WinForms应用程序响应,而连接字符串等待超时? - Ted Spence
@cshemby:那正是我正在做的。 - Chris Dargis

0

感谢大家的意见。

我已经想出了解决方案。在阅读WPF Dispatcher 的工作方式之后,我确定您可以获取 UI 线程的 Dispatcher 对象:

//...

dbTool = new DBTool();
// Initialize the connection string
// Disable some UI
Thread thread = new Thread(new ThreadStart(
        delegate()
        {
            dbTool.connectToDB();
            UIControl.Dispatcher.BeginInvoke(
              new Action(
                  update
            ));
        }
));
thread.Start();

//.....

void update()
{
    if (dbTool.validString)     // If the connection string was valid
    {
        // Re-enable controls
    }
    else     // Invalid connection string
    {
        // Keep controls disabled if no connection could be created
    }
}

这确实会在不同的线程上测试连接字符串,使应用程序的其余部分保持响应。


0

尝试

dbtool tool = e.result as dbtool;

如果您在dbTool中有一个变量,当查询完成时会设置为true或false,那么您应该能够调用它。
tool.variable = true/false

感谢您的回复,但正如我在问题中所说,尝试在bw_RunWorkerCompleted中访问e.Result会引发异常。 - Chris Dargis
你的connectToDB是什么样子的? - cshemby
样板连接池。 - Chris Dargis

0

来自 DBConnection的文档

该类型的任何公共静态成员(在Visual Basic中为Shared)都是线程安全的。 任何实例成员都不能保证线程安全。

换言之,不同的线程永远不应该共享数据库连接,因为实例无法安全地共享。正如Ted Spence建议的那样,您应该仅在需要时创建连接(并在使用完后将其.Dispose())。 .NET具有内置的连接池机制,可以很好地确保在可能的情况下重用连接,但持有连接的时间比绝对必要的时间长可能会干扰它这样做的能力。


抱歉如果之前表述不够清晰,但我并没有共享连接。我只是想要一个线程在应用程序保持打开状态的同时测试连接字符串。 - Chris Dargis

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