Вызвать делегата в главном потоке - Нет интерфейса - PullRequest
0 голосов
/ 24 мая 2019

Я читал всевозможные вопросы, которые похожи на то, что я пытаюсь сделать, но достаточно разные, чтобы они, похоже, не подходили. У меня есть проект C # для создания хранимой процедуры CLR. Я нахожусь в процессе улучшения производительности хранимой процедуры CLR путем многопоточности. (У него есть набор вложенных циклов, и в самом внутреннем цикле я вызываю Parallel.ForEach, чтобы запустить их все в своих собственных потоках.) Ну, иногда выполняемая обработка должна выполнять запрос к базе данных. Это делается с помощью контекстного соединения, которое сначала запустило хранимую процедуру. И вот моя проблема. SQL Server не позволит вам получить доступ к контекстному соединению из дочернего потока. Если вы собираетесь получить доступ к контекстному соединению, вы должны выполняться в главном потоке.

Поскольку это не проект WinForms, я не могу использовать BeginInvoke . (И я знаю, что есть аналогичная команда для приложений WPF.) И я видел несколько постов, в которых обсуждается использование SynchronizationContext для этого. Но мой основной поток не имеет SynchronizationContext для ссылки. (Я думаю, что он создан первым элементом управления, размещенным в потоке?) Мне нужно выяснить, как перенаправить выполнение обратно в основной поток, достаточно для доступа к контекстному соединению. Я все еще немного новичок в работе с многопоточными приложениями. Поэтому я прошу прощения, если мое использование терминологии было плохим или неточным.

Спасибо.

Edit:

Итак, основываясь на комментариях и ответах, я пытался внести некоторые изменения, но я боюсь, что либо они не работают, либо я просто что-то не получаю. Поэтому я решил опубликовать упрощенный код в качестве примера того, что я сейчас делаю. (Помните, что этот пример не работает, поскольку выполнение запроса выполняется в дочерних потоках, и только основной поток может использовать контекстное соединение.)

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 ]

1 голос
/ 24 мая 2019

Попробуйте этот рецепт, основываясь на комментариях @ 0liveradam8.

  1. Создайте потокобезопасную очередь, например, ConcurrentQueue .
  2. Установите все свои потоки в режиме запуска«;каждый поток выделит дескриптор ожидания;в этом случае подойдет AutoResetEvent .
  3. Когда каждый поток должен получить доступ к контексту, поставьте в очередь структуру, которая содержит три фрагмента информации: Func, который использует контекст, потокдескриптор ожидания и место для хранения результатов Func.
  4. В главном потоке в цикле исключите элемент из очереди, вызовите Func, сохраните результат в элементе, а затем подайте сигналдескриптор ожидания.

Выше будет маршалировать вызовы контекста к основному потоку и возвращаться к вызывающему потоку.Если поток должен сделать это несколько раз, это нормально, потому что ваш дескриптор ожидания будет автоматически сброшен до не сигнализированного, что позволяет использовать его повторно.

Потоки будут заблокированы во время использования контекста, что означает параллелизмнесколько ниже, но вы все равно получите, вероятно, то, что вам нужно, и это будет безопасно.

0 голосов
/ 29 мая 2019

Есть ли причина, по которой вам нужно использовать Context Connection?Используете ли вы какие-либо специфичные для сеанса функции, например, являетесь ли вы частью существующей транзакции, или читаете из существующих временных таблиц, или используете CONTEXT_INFO / SESSION_CONTEXT?

Есть ли причина, по которой вы просто не 't использовать обычное / внешнее соединение?

(на эти вопросы ответили следующие цитаты)

Мы хотим, чтобы это работало с безопасностью пользователя, настройками соединения и т. д. Кроме того,, если я пытаюсь открыть соединение из кода, SQL Server вызывает исключение.Так что, честно говоря, я не особо задумывался.Открытие другого соединения было запрещено, и мы все равно хотели использовать контекстное соединение.Вот что мы сделали.

  1. Работать с безопасностью пользователя достаточно просто:

    1. Если пользователь является логином на SQL Server, передайте этиучетные данные вместе в ConnectionString.Это может быть более трудным, если есть много пользователей, которые могут выполнять этот код, хотя вы могли бы передать пользователю свои учетные данные в качестве входных параметров для хранимой процедуры SQLCLR и построить из них строку подключения.
    2. Если пользователь является логином Windows, реализуйте олицетворение.Вы можете получить Windows Security Principal (или любой другой) от SqlContext.Затем выполните:

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

      Это не точно, но близко.

  2. Не уверен, почему вы получите исключение при открытии обычного соединения, если толькочто-то не так со строкой соединения.Было бы полезно получить точное (и полное) сообщение об ошибке, хотя звучит так, как будто вы уже прошли эту проблему.

Хорошо, так что мы на самом деле закончили с этим,Для этих запросов нам не требуется контекстное соединение.Таким образом, мы смогли избежать открытия новых соединений, вместо того чтобы выяснить, как эффективно переключать потоки.

Отлично!Рад слышать, что это все-таки сработало ?.Поскольку контекстное соединение не было обязательным , кажется, что потребовалось бы больше времени / усилий, чтобы выяснить переключение потоков, чем это стоило бы.И код хотел бы быть немного более сложным (то есть сложнее в обслуживании), чем то, что вы в итоге сделали.

Я подозреваю, что если бы мы с самого начала спроектировали это для выполнения этих запросовв главном потоке, что это не было бы такой проблемой.Но, похоже, это делает работу.

Правда, это могло бы быть проще, если бы вы спланировали эту часть с самого начала.Тем не менее, что бы ввел пункт-утверждение на этой особой связи, которая бы снизить производительность (даже если только незначительно).Даже несмотря на то, что Context Connection быстрее открывать / закрывать, чем обычное соединение, в зависимости от того, сколько времени потребуется для получения результатов из запросов, различные потоки могут потенциально ждать намного дольше, чем несколько лишних миллисекунд, потребляемых при установлении регулярногосоединения.Это, плюс повышенная сложность кода, вполне может стоить того, если вам требуются специфичные для сеанса функции (т. Е. Работа в активной транзакции, работа с локальными временными объектами, использование CONTEXT_INFO и / или SESSION_CONTEXT и т. Д.),но в противном случае, вероятно, нет.


Для получения дополнительной информации о работе с SQLCLR в целом, пожалуйста, посетите: SQLCLR Info

...