Использование IHttpAsyncHandler для асинхронного вызова WebService
Вот основные настройки. У нас есть приложение ASP.Net WebForms со страницей, на которой есть приложение Flash, которому необходим доступ к внешней веб-службе. Из-за (я полагаю, безопасности) ограничений во Flash (не спрашивайте меня, я вообще не эксперт по Flash), мы не можем подключиться к веб-службе напрямую из Flash. Обходной путь заключается в создании прокси в ASP.Net, который будет вызывать приложение Flash, которое, в свою очередь, вызовет Web Service и перенаправит результаты обратно в приложение Flash.
Тем не менее, веб-сайт имеет очень высокий трафик, и проблема в том, что если веб-служба вообще зависает, потоки запросов ASP.Net начнут выполнять резервное копирование, что может привести к серьезному истощению потоков. Чтобы обойти это, я решил использовать IHttpAsyncHandler, который был разработан именно для этой цели. В нем я буду использовать WebClient для асинхронного вызова веб-службы и пересылки ответа обратно. В сети очень мало примеров того, как правильно использовать IHttpAsyncHandler, поэтому я просто хочу убедиться, что я не делаю это неправильно. Я основываю свое использование на примере, показанном здесь: http://msdn.microsoft.com/en-us/library/ms227433.aspx
Вот мой код:
internal class AsynchOperation : IAsyncResult
{
private bool _completed;
private Object _state;
private AsyncCallback _callback;
private readonly HttpContext _context;
bool IAsyncResult.IsCompleted { get { return _completed; } }
WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
Object IAsyncResult.AsyncState { get { return _state; } }
bool IAsyncResult.CompletedSynchronously { get { return false; } }
public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
{
_callback = callback;
_context = context;
_state = state;
_completed = false;
}
public void StartAsyncWork()
{
using (var client = new WebClient())
{
var url = "url_web_service_url";
client.DownloadDataCompleted += (o, e) =>
{
if (!e.Cancelled && e.Error == null)
{
_context.Response.ContentType = "text/xml";
_context.Response.OutputStream.Write(e.Result, 0, e.Result.Length);
}
_completed = true;
_callback(this);
};
client.DownloadDataAsync(new Uri(url));
}
}
}
public class MyAsyncHandler : IHttpAsyncHandler
{
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
var asynch = new AsynchOperation(cb, context, extraData);
asynch.StartAsyncWork();
return asynch;
}
public void EndProcessRequest(IAsyncResult result)
{
}
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
}
}
Теперь все это работает, и я думаю, что это должно сработать, но я не уверен на 100%. Кроме того, создание собственного IAsyncResult кажется немного излишним, мне просто интересно, есть ли способ использовать IAsyncResult, возвращенный из Delegate.BeginInvoke, или, может быть, что-то еще. Любые отзывы приветствуются. Спасибо!!
1 ответ
Вау, да, вы можете сделать это намного проще / чище, если вы используете.NET 4.0, используя параллельную библиотеку задач. Проверь это:
public class MyAsyncHandler : IHttpAsyncHandler
{
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
// NOTE: the result of this operation is void, but TCS requires some data type so we just use bool
TaskCompletionSource<bool> webClientDownloadCompletionSource = new TaskCompletionSource<bool>();
WebClient webClient = new WebClient())
HttpContext currentHttpContext = HttpContext.Current;
// Setup the download completed event handler
client.DownloadDataCompleted += (o, e) =>
{
if(e.Cancelled)
{
// If it was canceled, signal the TCS is cacnceled
// NOTE: probably don't need this since you have nothing canceling the operation anyway
webClientDownloadCompletionSource.SetCanceled();
}
else if(e.Error != null)
{
// If there was an exception, signal the TCS with the exception
webClientDownloadCompletionSource.SetException(e.Error);
}
else
{
// Success, write the response
currentHttpContext.Response.ContentType = "text/xml";
currentHttpContext.Response.OutputStream.Write(e.Result, 0, e.Result.Length);
// Signal the TCS that were done (we don't actually look at the bool result, but it's needed)
taskCompletionSource.SetResult(true);
}
};
string url = "url_web_service_url";
// Kick off the download immediately
client.DownloadDataAsync(new Uri(url));
// Get the TCS's task so that we can append some continuations
Task webClientDownloadTask = webClientDownloadCompletionSource.Task;
// Always dispose of the client once the work is completed
webClientDownloadTask.ContinueWith(
_ =>
{
client.Dispose();
},
TaskContinuationOptions.ExecuteSynchronously);
// If there was a callback passed in, we need to invoke it after the download work has completed
if(cb != null)
{
webClientDownloadTask.ContinueWith(
webClientDownloadAntecedent =>
{
cb(webClientDownloadAntecedent);
},
TaskContinuationOptions.ExecuteSynchronously);
}
// Return the TCS's Task as the IAsyncResult
return webClientDownloadTask;
}
public void EndProcessRequest(IAsyncResult result)
{
// Unwrap the task and wait on it which will propagate any exceptions that might have occurred
((Task)result).Wait();
}
public bool IsReusable
{
get
{
return true; // why not return true here? you have no state, it's easily reusable!
}
}
public void ProcessRequest(HttpContext context)
{
}
}