【问题标题】:Invoke delegate on main thread - No UI在主线程上调用委托 - 无 UI
【发布时间】:2019-05-24 19:40:53
【问题描述】:

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

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

谢谢。

编辑:

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

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)。希望这段代码有用。再次感谢。

【问题讨论】:

  • 你能不能有一个队列来在主线程上存储委托,让子线程添加他们想要调用的任何委托,最后让主线程循环遍历列表并执行委托?
  • 我不确定这将如何工作。子线程需要它们尝试执行的 SQL 查询的结果才能完成它们的处理。
  • 您可以在主线程中使用额外的数据结构来存储查询结果,并让子线程从中获取结果
  • @DeadZone ,您是否有理由需要 使用上下文连接?您是否使用任何特定于会话的功能,例如作为现有事务的一部分,或从现有临时表中读取,或使用CONTEXT_INFO / SESSION_CONTEXT?您是否有理由不使用常规/外部连接?
  • @Solomon,好的,所以我们实际上最终选择了这个。对于这些查询,我们不需要上下文连接。所以我们能够摆脱打开新连接,而不是弄清楚如何有效地切换线程。我怀疑,如果我们从一开始就将其设计为在主线程上运行这些查询,那么它就不会成为这样的问题。但这似乎完成了工作。所以谢谢你。

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


【解决方案1】:

根据@0liveradam8 的 cmets 试试这个食谱。

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

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

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

【讨论】:

  • 我刚刚添加了一个编辑部分。我不知道我是否遗漏了某些东西,或者这是否不适用于我所拥有的东西,但是我无法以一种可行的方式将其合并到我的代码中。
【解决方案2】:

您需要使用上下文连接是否有原因?您是否使用任何特定于会话的功能,例如作为现有事务的一部分,或从现有临时表中读取,或使用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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-09
    • 2015-10-24
    • 2014-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多