Увеличит ли многопоточность производительность метода в серваке WCF?

У меня есть служба WCF, которая размещена в IIS6. Важная часть метода выглядит следующим образом:

public MyUser[] GetUsers(string appName, string[] names)
{
    List<User> users = new List<User>();
    foreach (string user in names)
    {
      MembershipUser mu = this.ADAMProvider.GetUser(user, false);  //Unmanaged call to AzMan
      if (mu != null)
      {
        users.Add(MyUser.CreateFrom(mu);
      }
    }
    return users.ToArray();
}

Производительность этого метода очень низкая, когда он вызывается с большим массивом имен пользователей (более 100 или около того). Это может занять более минуты, чтобы вернуться. Кроме того, если этот метод вызывается одновременно более чем одним клиентом, он истекает. Я даже видел, как это обрушило пул приложений. Обратите внимание, что в цикле выполняется вызов AzMan. AzMan - неуправляемый COM-компонент.

Для повышения производительности я рассматриваю многопоточный подход. .NET 4 не является опцией, поэтому Parallel.For не является опцией, но сделать эквивалент в 3.5 есть.

Мой вопрос заключается в том, будет ли создание группы потоков (затем ожидание всех перед возвратом) действительно повысить производительность? Есть ли опасность в этом в размещенной на IIS6 службе WCF?

2 ответа

Решение

Во-первых, я должен отметить, что компонент COM может быть проблемой в зависимости от состояния квартиры. Однопоточные квартирные объекты могут работать только в одном потоке. В объектах STA выполняется автоматическая операция маршалинга, которая эффективно сериализует все вызовы к нему, поэтому, как бы вы ни старались, вы не сможете получить никакого распараллеливания. Даже если бы это был объект MTA, может возникнуть проблема с GetUser метод, если он не предназначен для поточно-ориентированного.

Но предполагая, что ничего из этого не является проблемой1, я бы использовал ThreadPool вместо того, чтобы создавать кучу потоков, чтобы сделать это. Вот как это может выглядеть.

public MyUser[] GetUsers(string appName, string[] names) 
{ 
  int count = 1; // Holds the number of pending work items.
  var finished = new ManualResetEvent(false); // Used to wait for all work items to complete.
  var users = new List<User>(); 
  foreach (string user in names) 
  {
    Interlocked.Increment(ref count); // Indicate that we have another work item.
    ThreadPool.QueueUserWorkItem(
      (state) =>
      { 
        try
        {
          MembershipUser mu = this.ADAMProvider.GetUser(user, false); 
          if (mu != null) 
          { 
            lock (users)
            {
              users.Add(MyUser.CreateFrom(mu); 
            }
          } 
        }
        finally
        {
          // Signal the event if this is the last work item.
          if (Interlocked.Decrement(ref count) == 0) finished.Set();
        }
      });
  } 
  // Signal the event if this is the last work item.
  if (Interlocked.Decrement(ref count) == 0) finished.Set();
  // Wait for all work items to complete.
  finished.WaitOne();
  return users.ToArray(); 
}

Одна запутанная вещь в шаблоне, который я использовал выше, состоит в том, что он обрабатывает основной поток (тот, который помещает в очередь работу), как если бы это был другой рабочий элемент. Вот почему вы видите контрольный и сигнальный код в конце цикла. Без этого существует очень тонкое состояние расы, которое может возникнуть между Set а также WaitOne звонки.

Кстати, я должен отметить, что TPL доступен в.NET 3.5 как часть загрузки Reactive Extensions.

1Я подозреваю, что одна из двух упомянутых мной проблем будет в реальности.

Обычно, я бы сказал, что это может помочь - однако, это может быть проблемой:

Кроме того, если этот метод вызывается одновременно более чем одним клиентом, время ожидания истекает. Я даже видел, как это обрушило пул приложений. Обратите внимание, что в цикле выполняется вызов AzMan. AzMan - неуправляемый COM-компонент.

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

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

Другие вопросы по тегам