Выполнение асинхронной операции, вызванной запросом веб-страницы ASP.NET - PullRequest
53 голосов
/ 23 марта 2009

У меня есть асинхронная операция, которая по разным причинам должна запускаться с помощью HTTP-вызова веб-страницы ASP.NET. Когда моя страница запрашивается, она должна начать эту операцию и немедленно вернуть подтверждение клиенту.

Этот метод также предоставляется через веб-сервис WCF, и он отлично работает.

При первой попытке было сгенерировано исключение, сообщающее:

Asynchronous operations are not allowed in this context.
Page starting an asynchronous operation has to have the Async
attribute set to true and an asynchronous operation can only be
started on a page prior to PreRenderComplete event.

Поэтому, конечно, я добавил параметр Async="true" в директиву @Page. Теперь я не получаю сообщение об ошибке, но страница блокируется до завершения асинхронной операции.

Как мне заставить работать настоящую страницу "забыл и забыл"?

Редактировать: Некоторый код для получения дополнительной информации. Это немного сложнее, чем это, но я попытался изложить общую идею.

public partial class SendMessagePage : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string message = Request.QueryString["Message"];
        string clientId = Request.QueryString["ClientId"];

        AsyncMessageSender sender = new AsyncMessageSender(clientId, message);
        sender.Start();

        Response.Write("Success");
    }
}

Класс AsyncMessageSender:

public class AsyncMessageSender
{
    private BackgroundWorker backgroundWorker;
    private string client;
    private string msg;

    public AsyncMessageSender(string clientId, string message)
    {
        this.client = clientId;
        this.msg = message;

        // setup background thread to listen
        backgroundThread = new BackgroundWorker();
        backgroundThread.WorkerSupportsCancellation = true;
        backgroundThread.DoWork += new DoWorkEventHandler(backgroundThread_DoWork);
    }

    public void Start()
    {
        backgroundThread.RunWorkerAsync();
    }

    ...
    // after that it's pretty predictable
}

Ответы [ 5 ]

31 голосов
/ 23 марта 2009

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

private delegate void DoStuff(); //delegate for the action

protected void Page_Load(object sender, EventArgs e)
{

}

protected void Button1_Click(object sender, EventArgs e)
{
    //create the delegate
    DoStuff myAction = new DoStuff(SomeVeryLongAction); 
    //invoke it asynchrnously, control passes to next statement
    myAction.BeginInvoke(null, null);
    Button1.Text = DateTime.Now.ToString();
}


private void SomeVeryLongAction()
{
    for (int i = 0; i < 100; i++)
    {
        //simulation of some VERY long job
        System.Threading.Thread.Sleep(100);
    }
}
30 голосов
/ 27 августа 2013

Если вы используете веб-формы, установите Ansync = "true" на странице .aspx, где вы делаете запрос.
<%@ Page Language="C#" Async="true" ... %>

26 голосов
/ 23 марта 2009

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

Причина, по которой вы используете атрибут Async, состоит в том, чтобы избежать блокировки потока. Это важно, потому что приложения ASP.NET используют пул потоков для обслуживания запросов, и доступно только относительно небольшое количество потоков. И если каждый вызов связывает поток во время ожидания вызова веб-службы, то вскоре вы попадете на достаточное количество одновременно работающих пользователей, и пользователям придется ждать, пока эти вызовы веб-службы не завершатся. Атрибут Async позволяет потоку возвращаться в пул потоков и обслуживать других одновременных посетителей вашего веб-сайта, а не заставлять его сидеть неподвижно, ничего не делая, ожидая возврата вызова веб-службы.

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

Вам нужно запустить свой собственный поток и сделать его потоком демона. Я не помню точный синтаксис для этого, но вы можете легко найти его в документе, выполнив поиск в документе BCL для "daemon". Это означает, что поток будет препятствовать завершению работы вашего приложения, пока оно активно, что важно, потому что ASP.NET и IIS оставляют за собой право «перерабатывать ваш процесс», когда они сочтут это необходимым, и если это происходит во время работы вашего потока, Ваша задача будет остановлена. Создание демона потока предотвратит это (за исключением некоторых возможных редких крайних случаев ... вы узнаете больше, когда найдете документацию по этому вопросу).

В этом потоке демонов вы начнете выполнять эти задачи. И после того, как вы попросили поток демонов выполнить задание, вы можете немедленно визуализировать свою страницу ... так что визуализация страницы произойдет немедленно.

Однако даже лучше, чем поток демона в вашем процессе ASP.NET, реализовать службу Windows для выполнения этой задачи. Пусть ваше приложение ASP.NET сообщит о задаче, которая должна быть выполнена Сервису. Нет необходимости в потоке демона и не нужно беспокоиться о том, что ваш процесс ASP.NET может быть переработан. Как вы говорите Службе выполнить задачу? Возможно, через WCF или вставив запись в таблицу базы данных, которую служба опрашивает. Или ряд других способов.

РЕДАКТИРОВАТЬ: Вот еще одна идея, которую я использовал ранее для этой же цели. Запишите информацию о своей задаче в очередь MSMQ. Пусть другой процесс (возможно, даже на другом компьютере) извлечет из этой очереди и выполнит трудоемкую задачу. Задание по вставке в очередь оптимизировано так, чтобы оно возвращалось как можно быстрее, поэтому ваш поток не будет блокироваться, пока данные, помещенные в очередь, отправляются по проводам или что-то в этом роде. Это один из самых быстрых способов отметить тот факт, что задачу необходимо выполнить, не дожидаясь ее выполнения.

3 голосов
/ 28 июня 2013

Вы можете обойти это ограничение довольно легко и даже не устанавливая Async в true.

public void Start()
{
    new Task(() =>
    {
        backgroundThread.RunWorkerAsync();
    }).Start();
}
1 голос
/ 29 декабря 2012

Если вы получаете эту ошибку при асинхронном вызове веб-службы, убедитесь, что добавили атрибут Async = 'true' в соответствии с инструкциями сообщение об исключении?

начало страницы

...