Подтвердить что ты не робот

Огонь и забыть метод async в asp.net mvc

Общие ответы, такие как здесь и здесь, чтобы не включать и не забывать вопросы не использовать async/await, но вместо этого использовать Task.Run или TaskFactory.StartNew, проходящий в синхронном методе.
Однако иногда метод, который я хочу запустить и забыть, является асинхронным, и нет эквивалентного метода синхронизации.

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

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

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

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

Итак, исходный вопрос...

например:

//External library
public async Task DeleteFooAsync();

В моем коде mvc asp.net я хочу вызвать DeleteFooAsync в режиме "огонь-и-забыть" - я не хочу задерживать ответ, ожидающий завершения 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). Однако это похоже на много кода оболочки каждый раз, когда я хочу вызвать метод async в режиме "огонь и забыть".

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

UPDATE:

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

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

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

Необработанное исключение: System.NullReferenceException: ссылка на объект не установлен в экземпляр объекта. в System.Web.ThreadContext.AssociateWithCurrentThread(BooleansetImpersonationContext)

Здесь обсуждается здесь и, как представляется, связано с SynchronizationContext, и "возвращенная задача была перенесена в состояние терминала до того, как все async работа выполнена" .

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

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

Мое понимание того, почему это работает, состоит в том, что StartNew получает новый поток для DeleteFooAsync для работы.

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

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

4b9b3361

Ответ 1

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

Если вы согласны принять это ограничение, у меня есть некоторый код в моем блоге, который будет регистрировать задачи во время выполнения 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());

Ответ 2

Начиная с .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

Ответ 3

Лучший способ справиться с этим - использовать метод 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");
}

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