Асинхронный доступ к веб-службам в шаблонах проектирования .NET - Событийно-управляемая модель посредника
ОГЛАВЛЕНИЕ
Событийно-управляемая модель посредника
Она может показаться сложной при получении посредника в .NET 2.0 путем выполнения того же самого в VS 2005. Назовите этот посредник WsClient2.WsTestRef2.Service и определите объект как _wsProxy2. Подобно BeginGetStock() в .NET 1.1, этот посредник предоставляет GetStockAsync() как пусковой метод. Но надо добавить обработчик события, действующий как предыдущий обратный вызов. Следующий Листинг-3 показывает код второго клиента:
// Листинг-3. Использование событийно-управляемой модели
private void buttonGetQuote_Click(object sender, EventArgs e)
{
textBoxResult.Text = "";
_tmStart = DateTime.Now.Ticks;
string symbol = comboBoxSymb.Text.ToUpper();
int timeout = int.Parse(textBoxTimeout.Text);
_wsProxy2 = new WsClient2.WsTestRef2.Service();
_wsProxy2.GetStockCompleted += new
GetStockCompletedEventHandler(OnGetStockCompleted);
_wsProxy2.GetStockAsync(symbol, timeout, symbol);
}
private void OnGetStockCompleted(Object sender,
GetStockCompletedEventArgs gca)
{
string symbol = (string)gca.UserState;
string result;
if (gca.Cancelled) // Вызывается CancelAsync
result = "Cancelled2, " + gca.UserState;
else
if (gca.Error != null)
{
WebException webEx = gca.Error as WebException;
if (webEx !=null && webEx.Status ==
WebExceptionStatus.RequestCanceled)
result = "Cancelled, " + "<" + symbol + ">";
else
result = "Exception, " + gca.Error.Message;
}
else
if (gca.Result == 0)
result = "Invalid, " + "'" + symbol + "'";
else
if (gca.Result < 0)
result = "TimeOut, " + "[" + symbol + "]";
else
result = "OK, " + gca.Result.ToString("F");
textBoxResult.Text = result + ", (" +
(DateTime.Now.Ticks - _tmStart) / 10000 + " ms)";
_wsProxy2 = null;
}
private void buttonCancel_Click(object sender, EventArgs e)
{
if (_wsProxy2 != null)
{
//_wsProxy2.CancelAsync("<<" +
// comboBoxSymb.Text.ToUpper() + ">>");
_wsProxy2.Abort();
}
}
В buttonGetQuote_Click() добавляется обработчик события OnGetStockCompleted() в _wsProxy2 и вызывается GetStockAsync() для запуска асинхронного запроса. Аналогично, первые два параметра – символ и время простоя, а третий UserState, подобный AsyncState в BeginGetStock(), возвращается позже в обработчике. Запрос можно отменить с помощью последующего _wsProxy2.Abort().
Что нового в OnGetStockCompleted()? При вызове его второй параметр gca типа GetStockCompletedEventArgs (кратко смотрите его в листинге-4) приносит всю готовую информацию. gca содержит 4 свойства: UserState типа object, Result типа double, Error типа Exception и флаг Cancelled. Сравните с логикой в обратном вызове (Листинг-2), большинство частей понятны и нет нужды повторять.
Единственная хитрость касается gca.Cancelled. Как видно в Листинге-3, этот флаг сознательно проверяется в начале OnGetStockCompleted() и помещается другая проверка в состояние RequestCanceled из gca.Error. Что именно было захвачено? Определенно, успех - gca.Error, а не gca.Cancelled, потому что вызов _wsProxy2.Abort() приводит к генерации WebException.
Что если надо перехватить gca.Cancelled как флаг отмены? Немного изменим посредник. Чтобы отличить отмененный запрос от gca.Error, отображается «Отменено2» для gca.Cancelled следующим образом:
Используется CancelAsync() посредника, изначально ничего не делающий, но вызывающий его базовый. Поэтому в buttonCancel_Click() (в Листинге-3) вызывается _wsProxy2.CancelAsync(), а не _wsProxy2.Abort().
Листинг-4 ниже показывает измененный посредник, куда добавлено 4 нумерованных комментария, чтобы указать изменения:
// Листинг-4. Измененный событийно-управляемый посредник
public partial class Service : SoapHttpClientProtocol
{
private System.Threading.SendOrPostCallback
GetStockOperationCompleted;
private bool useDefaultCredentialsSetExplicitly;
// 1. Добавлен управляющий флаг _done
private bool _done = true;
public Service() { ... }
public new string Url { ... }
public new bool UseDefaultCredentials { ... }
public event GetStockCompletedEventHandler GetStockCompleted;
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(...)]
public double GetStock(string symbol, int timeout)
{
object[] results = Invoke("GetStock",
new object[] {symbol, timeout});
return ((double)(results[0]));
}
... ... ...
public void GetStockAsync(string symbol, int timeout, object userState)
{
// 2. Инициализируется _dene – не готово
_done = false;
if (GetStockOperationCompleted == null)
{
GetStockOperationCompleted = new
System.Threading.SendOrPostCallback(
OnGetStockOperationCompleted);
}
this.InvokeAsync("GetStock", new object[] {symbol, timeout},
GetStockOperationCompleted, userState);
}
private void OnGetStockOperationCompleted(object arg)
{
// 3. Когда завершено без отмены, возбуждается событие
if (GetStockCompleted != null && !_done)
{
_done = true;
InvokeCompletedEventArgs invokeArgs = (InvokeCompletedEventArgs)(arg);
GetStockCompleted(this,
new GetStockCompletedEventArgs(invokeArgs.Results,
invokeArgs.Error,
invokeArgs.Cancelled,
invokeArgs.UserState));
}
}
public new void CancelAsync(object userState)
{
// 4. Если готово, обработка не идет
if (_done) return;
// Вызвана отмена. Готово и возбуждается событие
_done = true;
GetStockCompleted(this,
new GetStockCompletedEventArgs(null,
null,
true,
userState));
// base.CancelAsync(userState);
}
private bool IsLocalFileSystemWebService(string url) { ... }
}
[System.CodeDom.Compiler.GeneratedCodeAttribute( ... )]
public delegate void GetStockCompletedEventHandler(object sender,
GetStockCompletedEventArgs e);
... ...
public partial class GetStockCompletedEventArgs
: System.ComponentModel.AsyncCompletedEventArgs
{
private object[] results;
internal GetStockCompletedEventArgs(object[] results,
System.Exception exception,
bool cancelled,
object userState) :
base(exception, cancelled, userState) { ... }
public double Result { get { ... } }
}
Для ясности опущено большинство неважных участков. Во-первых, определяется флаг _done в классе и устанавливается в true, так как нет обрабатывающихся запросов. Во-вторых, в GetStockAsync(),_done инициализируется в false (не готово). GetStockAsync() регистрирует свой собственный внутренний обратный вызов OnGetStockOperationCompleted() и запускает асинхронный запрос с помощью InvokeAsync(). Как только OnGetStockOperationCompleted() вызывается для завершенного запроса, он возбуждает событие GetStockCompleted(), вызывающее обработчик OnGetStockCompleted(), добавленный ранее в _wsProxy2.
Затем, третье изменение происходит в OnGetStockOperationCompleted(). Добавляется проверка _done, чтобы запретить возбуждение, если _done уже установлен в true в CancelAsync(), что является следующим четвертым изменением. Как только пользователь вызывает CancelAsync() для отмены, если запрос завис (_done равен false), _done устанавливается в true и возбуждается событие отмены – событие отправляет аргумент GetStockCompletedEventArgs с флагом Cancelled, установленным в true.
Приведенный пример помогает понять, как работает событийно-управляемый посредник. Но в реальном производственном сценарии такое изменение посредника не рекомендуется. Если посредник восстанавливается позже, любые изменения кода будут потеряны. Поэтому желательно использовать Abort() вместо CancelAsync().