WCF REST не обрабатывается асинхронно - PullRequest
7 голосов
/ 11 мая 2011

В настоящее время мы внедряем новую службу REST WCF в IIS для нашего сайта, и на некоторых страницах мы можем выполнять несколько вызовов AJAX с использованием JQuery в асинхронном режиме. Проблема в том, что кажется, что WCF (на стороне сервера) выполняется синхронно.

При загрузке страницы мы делаем 3 отдельных вызова для 3 разных методов. Используя логирование, я вижу, как все они попадают в файл global.asax в пределах 5 мс друг от друга Оттуда в журнале показывается все, что выполняется в порядке выхода из global.asax (не обязательно в порядке, в котором мы делали вызовы со страницы через javascript). Я ожидал, что каждый звонок получит собственную ветку и вернется индивидуально. Даже при подключении с помощью отладчика я вижу, что следующий метод не будет выполнен, пока я не перейду к текущему включенному методу.

Вот контракты на работу для трех методов, которые я «подумал», которые я реализовал для использования асинхронной модели.

    [OperationContract(AsyncPattern = true)]
    [WebInvoke(
        Method = "POST"
         , UriTemplate = "/ListUserPreferences"
        , BodyStyle = WebMessageBodyStyle.Wrapped
        , ResponseFormat = WebMessageFormat.Json
        , RequestFormat = WebMessageFormat.Json
    )]
    IAsyncResult BeginListUserPreferences(AsyncCallback callback, object state);
    Result<List<Data.EnumerationItem<UserPreferenceType>>> EndListUserPreferences(IAsyncResult asyncResult);

    [OperationContract(Name = "GetUserSecure", AsyncPattern = true)]
    [WebInvoke(
        Method = "POST"
         , UriTemplate = "/GetUser"
        , BodyStyle = WebMessageBodyStyle.Wrapped
        , ResponseFormat = WebMessageFormat.Json
        , RequestFormat = WebMessageFormat.Json
    )]
    IAsyncResult BeginGetUser(AsyncCallback callback, object state);
    Result<Data.User> EndGetUser(IAsyncResult asyncResult);

    [OperationContract(AsyncPattern = true)]
    [WebInvoke(
        Method = "POST"
         , UriTemplate = "/ListWithAttributes"
        , BodyStyle = WebMessageBodyStyle.Wrapped
        , ResponseFormat = WebMessageFormat.Json
        , RequestFormat = WebMessageFormat.Json
    )]
    IAsyncResult BeginListWithAttributes(int index, int pageSize, AsyncCallback callback, object state);
    Result<PagedCollection<Data.Attribute>> EndListWithAttributes(IAsyncResult asyncResult);

Вот пример одной из реализаций в сервисе.

    public IAsyncResult BeginGetUser(AsyncCallback callback, object state)
    {
        var asyncResult = new CompletedAsyncResult<Result<Data.User>>(state);
        asyncResult.Result = new Result<Data.User>();

        asyncResult.Result.Value.UserId = Guid.Empty;
        asyncResult.Result.Value.DisplayName = "asdfasd";
        asyncResult.IsCompleted = true;           

        callback(asyncResult);

        return asyncResult;
    }

    public Result<Data.User> EndGetUser(IAsyncResult asyncResult)
    {
        return ((CompletedAsyncResult<Result<Data.User>>)asyncResult).Result;
    }

Вот атрибуты, которые мы имеем в классе реализации сервиса.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]

Может ли кто-нибудь дать некоторое представление о том, почему они выполняются синхронно, и что мне нужно сделать, или, по крайней мере, указать мне, что нужно сделать, чтобы они выполнялись асинхронно?

UPDATE

Я взял ответ Мэтта и переместил свою логику в вызовы функции End и следовал за этой записью в блоге о том, как он сделал это более подробно Загрузка больших файлов в службу самообслуживания WCF . Однако я не мог заставить методы End вызывать его технику. Я также начинаю думать, что поступаю неправильно. Поскольку, просматривая журналы, моя пользовательская служба аутентификации выполняется перед каждым вызовом службы, что происходит до того, как какие-либо операции асинхронного метода даже сработают. После дальнейшего изучения я посмотрел на ThreadId каждого запроса, поступающего в IIS, а затем выполняющего операции. Похоже, что WCF использует только 1 рабочий поток ... точка. Не имеет значения, сколько запросов я отправляю за один раз в IIS. Например, если я отправляю 3 запроса, я вижу, что они все приходят в разное время (в пределах миллисекунд друг от друга) и все получают свой собственный поток. Но затем кажется, что WCF просто ставит их в очередь и выполняет их в таком порядке, потому что все они выполняются в одном потоке, включая вызовы службы аутентификации.

Ответы [ 2 ]

2 голосов
/ 11 мая 2011

Из вашего примера мне кажется, что вы выполняете всю работу до завершения вызова "Begin"; Это распространенная ошибка, которую я обнаружил при изучении асинхронного шаблона.

Хотя я не знаком с его приложением в WCF, модель Async, как правило, представляет собой метод Begin, который запускает новый поток с объектом IAsyncResult, который будет обновлен этим новым потоком. Чтобы «завершить» действие, когда IsCompleted установлено на true, ожидается, что исходный вызывающий объект передаст исходный объект IAsyncResult обратно в соответствующий метод End, который возвращает результат. Тривиальная реализация выглядит следующим образом:

    static Func<string> getUser;
    public static IAsyncResult BeginGetUser(AsyncCallback callback, object state)
    {
        getUser = () =>
            {
                Thread.Sleep(2000);
                return "finished";
            };
        return getUser.BeginInvoke(callback, state);
    }

    public static string EndGetUser(IAsyncResult asyncResult)
    {
        return getUser.EndInvoke(asyncResult);
    }

Звонки на него могут выглядеть так:

var result = BeginGetUser(null, null);
string value = EndGetUser(result);

Конечно, это тривиальный случай: процитировать http://kennyw.com/work/indigo/258, «Если вы не делаете что-то« изначально асинхронное », то вам не следует использовать AsyncPattern = true».

К счастью, с C # 5.0 или Async CTP, выпущенным Microsoft, асинхронный шаблон .Net может уйти в прошлое.

0 голосов
/ 11 мая 2011

Чтобы сделать вещи асинхронными, вы обычно составляете их вместе с другими асинхронными вещами.Если вы используете синхронные методы только с асинхронными методами, то нет смысла использовать шаблон асинхронности.Например, если весь ваш код выполняет работу над пулом потоков, вы ничего не сделали, потому что вы вернули поток в пул потоков, переместив код асинхронно, но украли его обратно из ASP.NET, запустив вашработать там.

Если вы делаете вызов базы данных, чтобы получить пользователя, и ваша база данных поддерживает асинхронные операции, вы можете создать что-то вроде этого:

public IAsyncResult BeginGetUser(AsyncCallback callback, object state)
{
  var taskFunc = Task<DbReader>.Factory.FromAsync(db.BeginGetUser, db.EndGetUser);
  return taskFunc.ContinueWith(task => {
    var reader = task.Result;
    reader.Read();
    return new Data.User {
      DisplayName = reader["displayName"] as string,
      UserId = Guid.Parse(reader["userId"] as string),
    }
   }
  );
}

public Result<Data.User> EndGetUser(IAsyncResult asyncResult)
{
  return (Task<User>)(asyncResult).Result;
}

Позвольте мне подчеркнуть кое-что: Asyncпрограммирование сложно.С Задачей это становится немного легче, но вам все еще нужно обернуть голову вокруг продолжений.Отладка - это рутинная и неправильная работа, и ваш код просто исчезает, и вы не знаете почему.Если вы пропустите исключение, оно появится в ветке финализатора, и вы не обязательно будете знать, почему.Кроме того, чтобы выполнить асинхронное программирование, вам необходимо навести порядок в многопоточном программировании, которое опасно.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...