在主线程上调用委托 - 否 UI

Invoke delegate on main thread - No UI

我一直在阅读各种各样的问题,这些问题与我正在尝试做的类似,但差异很大,以至于它们似乎并不适用。我拥有的是一个用于创建 CLR 存储过程的 C# 项目。我正在通过多线程提高 CLR 存储过程的性能。 (它有一组嵌套循环,在最内层的循环中,我在它们自己的线程上调用 Parallel.ForEach 到 运行。)好吧,有时完成的处理需要执行一个查询数据库。这是使用首先启动存储过程的上下文连接完成的。这是我的问题。 SQL 服务器不允许您从子线程访问上下文连接。如果要访问上下文连接,则必须在主线程上执行。

由于这不是 WinForms 项目,我无法使用 BeginInvoke。 (而且我知道 WPF 应用程序有一个类似的命令。)而且我已经看到几个 post 讨论使用 SynchronizationContext 来执行此操作。但是我的主线程没有 SynchronizationContext 来引用。 (我认为这是由放置在线程上的第一个控件创建的?)我需要弄清楚如何将执行编组回主线程,刚好足以访问上下文连接。我对使用多线程应用程序还是有些陌生。因此,如果我的术语使用不当或不准确,我深表歉意。

谢谢。

编辑:

因此,根据评论和回答,到目前为止,我尝试进行一些更改,但我担心要么它们不起作用,要么我只是没有得到一些东西。所以我想我会 post 一些简化的代码作为我目前正在做的事情的例子。 (请记住,此示例不起作用,因为查询执行发生在子线程上,只有主线程可以使用上下文连接。)

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)。希望这段代码对您有所帮助。再次感谢。

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

  1. 创建一个thread-safe队列,例如ConcurrentQueue.
  2. 将所有线程设置为'runnin';每个线程都会分配一个等待句柄;在这种情况下,AutoResetEvent 是合适的。
  3. 当每个线程都必须访问上下文时,将包含三部分信息的结构入队:使用上下文的 Func、线程的等待句柄和存储 [= 结果的位置10=].
  4. 在循环的主线程中,出列一个项目,调用Func,将结果存储在项目中,然后向等待句柄发出信号。

上面的代码会将对上下文的调用编组到主线程,并将结果返回给调用线程。如果一个线程需要多次执行此操作,那没关系,因为您的等待句柄将从 auto-reset 到 not-signaled,因此可以重复使用。

线程在使用上下文的时候会被阻塞,这意味着并发性会有所降低,但你仍然可能得到你需要的东西,而且是安全的。

您是否需要使用上下文连接?您是否正在使用任何 Session-specific 功能,例如作为现有事务的一部分,或从现有临时表中读取,或使用 CONTEXT_INFO / SESSION_CONTEXT?

有没有你不使用常规/外部连接的原因?

(这些问题已用以下引用的陈述回答)

We want this to run with the user's security, connection settings, etc. Besides, if I attempt to open a connection from within the code, SQL Server raises an exception. So, to be honest, I haven't given that much thought. Opening another connection wasn't allowed, and we wanted to use the context connection, anyway. So that's what we did.

  1. 运行 用户安全很简单:

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

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

      这不准确,但很接近。

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

Okay, so we actually ended up going with this. For these queries, we do not require the context connection. So we were able to get away with opening up new connections, instead of figuring out how to switch threads effectively.

太棒了!很高兴听到它毕竟成功了😺 .由于上下文连接不是 必需的,似乎要花更多的时间/精力来弄清楚 thread-switching 比它本来的价值。而且,代码会比你最终得到的要复杂得多(即更难维护)。

I suspect that if we had architected this from the beginning to run these queries on the main thread that it wouldn't have been such an issue. But this seems to get the job done.

是的,如果您从一开始就计划好这部分,事情可能不会那么复杂。但是,这会在该单一连接上引入 point-of-contention,这会降低性能(即使只是轻微的)。即使上下文连接比常规连接快 open/close,取决于从查询返回结果需要多长时间,各种线程等待的时间可能比被消耗的额外几毫秒要长得多建立定期联系。如果您需要 session-specific 功能(即在活动事务中工作,使用本地临时对象,使用 CONTEXT_INFO and/or SESSION_CONTEXT, 等), 但除此之外可能不是。


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