在主线程上调用委托-没有UI

时间:2019-05-24 19:40:54

标签: c# sql-server multithreading database-connection sqlclr

我一直在阅读各种各样的问题,这些问题与我正在尝试的问题类似,但又相距甚远,似乎无法应用。我所拥有的是一个C#项目,用于创建CLR存储过程。我正在通过多线程提高CLR存储过程的性能。 (它具有一组嵌套循环,在最内层的循环中,我正在调用Parallel.ForEach在它们自己的线程上运行它们。)嗯,有时完成的处理需要对数据库执行查询。 。首先使用上下文连接启动存储过程。这是我的问题。 SQL Server不允许您从子线程访问上下文连接。如果要访问上下文连接,则必须在主线程上执行。

由于这不是WinForms项目,所以我无法使用 BeginInvoke 。 (我知道WPF应用程序也有类似的命令。)而且我看过几篇文章,讨论了如何使用 SynchronizationContext 来做到这一点。但是我的主线程没有要引用的 SynchronizationContext 。 (我认为这是由放置在线程上的第一个控件创建的?)我需要弄清楚如何将执行封送回主线程的刚好足以访问上下文连接。对于使用多线程应用程序,我还是有些新手。因此,对我的术语使用不当或不准确表示歉意。

谢谢。

编辑:

因此,到目前为止,根据评论和答案,我尝试进行一些更改,但是,恐怕它们要么不起作用,要么我什么都没得到。所以我想我会发布一些简化的代码作为我目前正在做的事的一个例子。 (请记住,此示例不起作用,因为查询执行是在子线程上进行的,并且只有主线程才能使用上下文连接。)

public class MyDatabaseProject
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static int MyClrStoredProcedure(...)
    {
        ProcessingEngine engine = new ProcessingEngine();
        engine.SomeQueryEvent += this.HandleSomeQueryEvent;

        ...  // Gather up some data to process.

        DataTable results = engine.Compute(...);

        ...  // Save the computed results DataTable.
    }

    private static void HandleSomeQueryEvent(object Sender, MyEventArgs e)
    {
        SqlConnection contextConn = new SqlConnection("context connection=true");
        contextConn.Open();

        foreach (string query in e.QueriesToExecute)
        {
            // Use the contextConnection to execute the query and store the results in MyEventArgs.
        }
        contextConn.Close();
    }
}

public class ProcessingEngine
{
    public DataTable Compute(...)
    {
        ... // Do stuff

        foreach(var timingIndicator in SomeCollection)
        {
            ... // Do stuff

            Parallel.ForEach(FormulasToProcessNow, new ParallelOptions { MaxDegreeOfParallelism = this.ConcurrencyLevel }, r =>
            {
                ... // Do stuff, including raising "SomeQueryEvent"

                ... // Do stuff with the results of the queries.
            });
        }
    }
}

所以让我困惑的是如何以一种可行的方式合并您的建议(例如ConcurrentQueue和AutoResetEvent)。希望这段代码对您有所帮助。再次谢谢你。

2 个答案:

答案 0 :(得分:1)

根据@ 0liveradam8的评论尝试此食谱。

  1. 创建一个线程安全队列,例如ConcurrentQueue
  2. 将所有线程设置为“ runnin”;每个线程将分配一个等待句柄;在这种情况下,AutoResetEvent是合适的。
  3. 当每个线程必须访问上下文时,请排队包含以下三条信息的结构:使用上下文的Func,线程的等待句柄以及用于存储{{1}的结果的位置}。
  4. 在循环的主线程中,使一个项目出队,调用Func,将结果存储在该项目中,然后发出等待句柄的信号。

以上内容将对上下文的调用编组到主线程,然后将结果返回给调用线程。如果一个线程需要多次执行此操作,那没关系,因为您的等待句柄将自动重置为未信号状态,从而可以重用。

在使用上下文时,线程将被阻塞,这意味着并发性有所降低,但是您仍然可以得到所需的东西,这将是安全的。

答案 1 :(得分:0)

是否有需要使用上下文连接的原因?您是否正在使用任何特定于会话的功能,例如作为现有事务的一部分,还是从现有临时表中进行读取,还是使用CONTEXT_INFO / SESSION_CONTEXT

您为什么不使用常规/外部连接?

(这些问题用以下引用的语句回答了)

  

我们希望它与用户的安全性,连接设置等一起运行。此外,如果我尝试从代码内打开连接,则SQL Server会引发异常。因此,老实说,我没有考虑太多。不允许打开另一个连接,无论如何我们都想使用上下文连接。这就是我们所做的。

  1. 使用用户的安全性运行很容易:

    1. 如果用户是SQL Server登录名,请在ConnectionString中传递这些凭据。如果有很多用户可能正在执行此代码,则这可能会更加困难,尽管您可以让用户将其凭据作为输入参数传递给SQLCLR存储过程,并从中建立连接字符串。
    2. 如果用户是Windows登录名,请实施模拟。您可以从SqlContext获取Windows安全主体(或其他)。然后做:

      using(impersonationContext = principal.Impersonate())
      {
        connection.Open();
      
        ...
      
        impersonationContext.Undo();
      }
      

      那不完全是,但是很接近。

  2. 不确定打开常规连接时为什么会出现异常,除非连接字符串出现问题。尽管听起来好像您现在已经通过了此问题,但获得准确的(完整的)错误消息会有所帮助。
  

好的,所以我们实际上结束了这一点。对于这些查询,我们不需要上下文连接。因此,我们能够摆脱建立新连接的麻烦,而不必弄清楚如何有效地切换线程。

太好了!听到一切顺利?很高兴?。由于不需要 上下文连接,因此似乎需要花费更多的时间/精力来解决线程切换问题。而且,代码要比最终的代码复杂得多(即难以维护)。

  

我怀疑,如果我们从一开始就设计了这种结构,以便在主线程上运行这些查询,那将不会是一个问题。但这似乎可以完成工作。

是的,如果您从头开始计划那部分,它可能会变得不那么复杂。但是,这将在该单一连接上引入争用点,这将降低性能(即使只是轻微的降低)。即使上下文连接的打开/关闭速度比常规连接要快,但取决于从查询中获取结果所需的时间,各个线程的等待时间可能比通过建立常规连接所消耗的几毫秒长得多连接。如果您需要特定于会话的功能(例如,在活动事务中工作,使用本地临时对象,使用CONTEXT_INFO和/或SESSION_CONTEXT,那再加上代码复杂性的增加就很值得了。等等),但否则可能不会。


有关一般使用SQLCLR的更多信息,请访问:SQLCLR Info