2013-08-29 3 views
45

общих ответы, такие как here и here к огню и забыть вопросов не использовать асинхронный/Await, но использовать Task.Run или TaskFactory.StartNew прохождение в синхронном методе вместо ,
Однако иногда метод, который я хочу использовать fire-and-forget, является асинхронным и нет эквивалентного метода синхронизации.и забыть метод асинхронного в ASP.NET MVC

Обновить примечание/предупреждение: Как показано ниже, Стивен Клири, опасно продолжать работу над запросом после того, как вы отправили ответ. Причина в том, что AppDomain может быть закрыт, пока эта работа все еще выполняется. Для получения дополнительной информации см. Ссылку в его ответе. В любом случае, я просто хотел указать на это заранее, чтобы я никого не посылал по неверному пути.

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

Было бы идеально, если бы у меня был доступ к механизму персистентности, как указал Стивен Клири, но, к сожалению, я этого не делаю в это время.

Я считал, что просто притворяюсь, что запрос DeleteFoo завершился нормально на стороне клиента (javascript), сохраняя запрос открытым, но мне нужна информация в ответе, чтобы продолжить, поэтому он будет держать вещи вверх.

Итак, оригинальный вопрос ...

, например:

//External library 
public async Task DeleteFooAsync(); 

В моем ASP.NET MVC код, который я хочу, чтобы позвонить DeleteFooAsync в огонь и забыл моды - Я не» t хотите сохранить ответ, ожидающий завершения DeleteFooAsync. Если по какой-то причине DeleteFooAsync сработает (или выбрасывает исключение), пользователь или программа не могут ничего сделать, поэтому я просто хочу зарегистрировать ошибку.

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

//In my code 
Task deleteTask = DeleteFooAsync() 

//In my App_Start 
TaskScheduler.UnobservedTaskException += (sender, e) => 
{ 
    m_log.Debug("Unobserved exception! This exception would have been unobserved: {0}", e.Exception); 
    e.SetObserved(); 
}; 

Существуют ли какие-либо риски при этом?

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

private void async DeleteFooWrapperAsync() 
{ 
    try 
    { 
     await DeleteFooAsync(); 
    } 
    catch(Exception exception) 
    { 
     m_log.Error("DeleteFooAsync failed: " + exception.ToString()); 
    } 
} 

, а затем вызвать, что с TaskFactory.StartNew (вероятно, обертывание в действие асинхронным). Однако это похоже на много кода оболочки каждый раз, когда я хочу вызвать метод async в режиме «огонь и забыть».

Мой вопрос в том, что это правильный способ вызова метода асинхронного использования в режиме «огонь и забыть»?

UPDATE:

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

[AcceptVerbs(HttpVerbs.Post)] 
public async Task<JsonResult> DeleteItemAsync() 
{ 
    Task deleteTask = DeleteFooAsync(); 
    ... 
} 

вызвало исключение:

Unhandled Exception: System.NullReferenceException: Ссылка на объект not s et к экземпляру объекта. на System.Web.ThreadContext.AssociateWithCurrentThread (BooleansetImpersonationContext)

Это обсуждается here и кажется, что делать с SynchronizationContext и «возвращенной задачей было перешли на терминальное состояние, прежде чем вся асинхронную работа завершена.

Таким образом, единственный метод, который работал был:

Task foo = Task.Run(() => DeleteFooAsync()); 

Мое понимание того, почему это работает, потому что StartNew получает новую нить для DeleteFooAsync работать.

К сожалению, предложение Скотта ниже не работает для обработки исключений в этом случае, поскольку foo больше не является задачей DeleteFooAsync, а скорее задачей Task.Run, поэтому не обрабатывает исключения из DeleteFooAsync. Мое UnobservedTaskException в конечном итоге вызвано, поэтому по крайней мере это все еще работает.

Итак, я догадываюсь, что вопрос по-прежнему стоит, как вы стреляете и забываете метод async в asp.net mvc?

+0

возможно дубликат [Как обрабатывать ненаблюдаемые исключений в ASP.NET MVC] (http://stackoverflow.com/questions/18501919/how-to-handle-unobserved-exceptions -in-asp-net-mvc) –

+0

lol. Это еще один вопрос, который я задал. Этот вопрос касается того, как обрабатывать незаметные исключения. Этот вопрос касается того, как делать вызовы метода «огонь и забухание», когда метод «огонь и забухание» является асинхронным. – acarlon

+0

Вы должны использовать 'Task.Run' вместо' TaskFactory.StartNew' в коде 'async' (http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx) , –

ответ

41

Прежде всего, позвольте мне отметить, что «огонь и забыть» почти всегда является ошибкой в ​​приложениях ASP.NET. «Огонь и забыть» - это только приемлемый подход, если вам все равно, действительно ли завершается DeleteFooAsync.

Если вы согласны принять это ограничение, у меня есть some code on my blog, который будет регистрировать задачи с помощью среды выполнения ASP.NET и принимает синхронную и асинхронную работу.

Вы можете написать метод одноразовую обертку для исключения лесозаготовительных как таковой:

private async Task LogExceptionsAsync(Func<Task> code) 
{ 
    try 
    { 
    await code(); 
    } 
    catch(Exception exception) 
    { 
    m_log.Error("Call failed: " + exception.ToString()); 
    } 
} 

И затем использовать BackgroundTaskManager из моего блога, как, например:

BackgroundTaskManager.Run(() => LogExceptionsAsync(() => DeleteFooAsync())); 

В качестве альтернативы, вы можете сохранить TaskScheduler.UnobservedTaskException и просто назовите это так:

BackgroundTaskManager.Run(() => DeleteFooAsync()); 
+8

Мне нравится этот ответ для его первого предложения. – usr

+3

Спасибо, это блестяще. Я вижу из блога, что причина, по которой это действительно плохо, заключается в том, что AppDomain можно снести во время пожара и забыть op. В моем случае DeleteFooAsync обрабатывается другой системой, поэтому нужно знать, что сообщение оставило мой процесс. Причина, по которой он срабатывает и забывается, заключается в том, что это довольно продолжительная операция, в которой я не хочу, чтобы пользователь ожидал ответа, и ни пользователь, ни сервер ничего не могут сделать об исключении, и есть не оказывает отрицательного воздействия на пользователя. Еще раз спасибо, я подумаю об этом. – acarlon

+0

Я обновил свой вопрос с предупреждением в начале и ссылкой на этот ответ. – acarlon

7

Лучший способ справиться с этим - использовать метод ContinueWith и передать в опции OnlyOnFaulted.

private void button1_Click(object sender, EventArgs e) 
{ 
    var deleteFooTask = DeleteFooAsync(); 
    deleteFooTask.ContinueWith(ErrorHandeler, TaskContinuationOptions.OnlyOnFaulted); 
} 

private void ErrorHandeler(Task obj) 
{ 
    MessageBox.Show(String.Format("Exception happened in the background of DeleteFooAsync.\n{0}", obj.Exception)); 
} 

public async Task DeleteFooAsync() 
{ 
    await Task.Delay(5000); 
    throw new Exception("Oops"); 
} 

Где я разместил свой ящик сообщений, вы бы поставили свой регистратор.

+0

Спасибо, это довольно аккуратно. Я предполагаю, что это означает дополнительную строку для каждого асинхронного вызова с огнем и забыть. Почему это лучше, чем обработка с помощью TaskScheduler.UnobservedTaskException? – acarlon

+0

Я предполагаю, что это позволяет более мелкозернистую обработку исключения. Например, вы можете регистрировать ошибки особым образом для некоторых частей кода или выполнять специальное действие (например, Exception было [TimeoutException] (http://msdn.microsoft.com/en-us/library/system .timeoutexception.aspx), возможно, попробуйте еще раз несколько раз, прежде чем сдаться и просто зарегистрировать ошибку), 'TaskScheduler.UnobservedTaskException' является глобальным вариантом, поэтому у вас нет этого элемента управления. –

+0

Да, это то, что я думал. Имеет смысл. – acarlon

12

с. NET 4.5.2, вы можете сделать следующее

HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => await LongMethodAsync()); 

Но это работает только в домене ASP.NET

Метод HostingEnvironment.QueueBackgroundWorkItem позволяет вам график небольшой фон рабочие элементы. ASP.NET отслеживает эти элементы, и мешает IIS внезапно прекратить рабочий процесс до тех пор, пока не завершится работа всех фоновых рабочих элементов . Этот метод нельзя назвать вне домена управляемого приложения ASP.NET.

Подробнее здесь: https://msdn.microsoft.com/en-us/library/ms171868(v=vs.110).aspx#v452

+0

Я не мог заставить его работать, используя обратный вызов 'async', вызывающий метод' await'ed внутри. Он войдет во вложенный метод, но никогда не завершится. Следующие работали для меня, хотя: HostingEnvironment.QueueBackgroundWorkItem (cancelationToken => LongMethodAsync()); – lionello

+0

Это не рекомендуется для длительных задач. –

+0

@Manish Почему? – Sinjai

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