2016-03-13 5 views
3

У меня есть хранимая процедура SQL Server, ответственная за обработку большого количества данных, которые занимают от 15 секунд до нескольких минут для запуска. Я пытаюсь выполнить его асинхронно, используя действие async MVC, вызванное запросом от клиента пользовательского интерфейса. Кажется, я все делаю так, как учит .NET-асинхронные книги и учебники, но, похоже, хранимая процедура прерывается вскоре после старта.Асинхронное выполнение долговременной хранимой процедуры через webservice

После запуска хранимая процедура добавляет запись в таблицу состояния для указания текущей работы. После успешного завершения хранимая процедура обновляет запись состояния с помощью флага успеха, а любые ошибки обрабатываются блоком TRY-CATCH, который обновляет запись состояния с помощью флага ошибки.

Это было тщательно протестировано в SQL Server Management Studio, поэтому я уверен, что хранимая процедура не может просто бомбить спокойно. Однако, когда хранимая процедура выполняется через асинхронный вызов (см. Ниже), запись о задании вставляется в таблицу состояния, но она не обновляется, что означает, что хранимая процедура никогда не достигает своего конца и прекращается в середине полета , возможно, из-за закрытого соединения с базой данных.

Хранимая процедура вызова заворачивают в этом ASync метод репозитория:

public async Task ProcessCatalogUpdateAsync(Guid updateID) 
    { 
     try 
     { 
      using (var cmd = new SqlCommand("ProcessCatalogUpdate", new SqlConnection(_connectionString))) 
      { 
       cmd.CommandType = CommandType.StoredProcedure; 
       cmd.CommandTimeout = 0; 
       cmd.Parameters.Add("@UpdateID", SqlDbType.UniqueIdentifier).Value = updateID; 

       cmd.Connection.Open();   

       await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); 
      } 
     } 
     catch (Exception ex) 
     { 
      var message = String.Format("Error processing catalog update ID={0}", updateID); 
      throw new DataException(message, ex); 
     } 
    } 

Действие контроллер выглядит следующим образом:

public async Task UpdateCatalog(string updateID) 
    { 
     var uid = Guid.Empty; // breakpoint here 
     if (!Guid.TryParse(updateID, out uid)) 
      throw new Exception("Bad UpdateID in request!"); 

     await _repository.ProcessCatalogUpdateAsync(uid); 

     System.Diagnostics.Trace.WriteLine("Catalog update completed"); // breakpoint here 
    } 

Я построил тестовую в виде просто HTML, которая асинхронно вызывает действие с использованием jQuery, например:

var requestURL = "/catalog/updateCatalog?updateID=" + $("#updateID").val(); 

    $.ajax({ 
     url: requestURL, 
     type: "GET", 
     error: function (xhr, status, errorText) { 
      alert("Error: " + errorText); // breakpoint here 
     }, 
     success: function() { 
      alert("Call returned!"); // breakpoint here 
     } 
    }); 

Я положил br eakpoints в обоих предупреждениях в JavaScript, а также в начале моего метода действий контроллера, чтобы подтвердить, что действие действительно выполнено. У меня также есть точка останова на линии System.Diagnostics сразу после вызова метода репозитория await, когда метод возвращается (хотя в отладочном сеансе может быть бессмысленным, поскольку контекст потока, вероятно, будет отличаться).

Это то, что происходит:

  • точку останова в начале UpdateCatalog действия ударил
  • Хранимая процедура вставляет новую строку в таблицу состояния
  • success обработчик JavaScript никогда не срабатывает
  • Таблица состояния никогда не обновляется ни с символом успеха, ни с ошибкой
  • Точка останова на System.Diagnostics никогда не будет h его

Что я делаю неправильно?


UPDATE

Как оказалось, была проблема в хранимой процедуре самого кода, который в самом конце было что-то делает, что принимает более 20 минут. Я теперь переписал его, чтобы работать на несколько порядков быстрее. Тем не менее, асинхронный материал по-прежнему работает не так, как я ожидаю.Событие jQuery AJAX success срабатывает только после того, как метод действия контроллера возобновляется после строки await, а не сразу.

Как изменить способ действия контроллера для выполнения в режиме «огонь и забухание»?

+0

", который занимает от 15 секунд до нескольких минут для запуска" - время по умолчанию для SqlConnection составляет 15 секунд. Вы 1) подтвердили, что соединение было обновлено с превышением времени ожидания, превышающим ожидаемое время выполнения? 2) Профилировали ли вы SQL Server, чтобы проверить, выполняется ли запрос как ожидалось? –

+1

'ConnectionTimeout' не имеет никакого отношения к тому, как долго выполняется команда, это' CommandTimeout'. – Crowcoder

+0

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

ответ

2

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

Изменить

await _repository.ProcessCatalogUpdateAsync(uid); 

в

_repository.ProcessCatalogUpdateAsync(uid); 

И переместить диагностический журнал внутри ProcessCatalogUpdateAsync (или в метод обертки). Теперь это огонь и забыли.

«огонь и забыть» имеет свои опасности. Работа может быть потеряна без предварительного уведомления или если слишком быстро инициируется одновременный пожар и забыть о работе, это может привести к сбою и перегрузке службы.

+0

Вы правы, что удаление 'await' и изменение метода действия контроллера для синхронизации, а не async сделали трюк. И вы правы, что в большинстве случаев «огонь и забыть» не был бы правильным подходом, за исключением того, что это очень специфический сценарий, когда мы должны так поступать, а затем определять результат операции, опросив таблицу состояния. –

+0

Все в порядке. Это форма очереди сообщений, и очередь остается постоянной. – usr

Смежные вопросы