Как я могу использовать async/wait для вызова веб-службы?
У меня есть webservice, написанный в Yii (php framework).
Я использую С# и Visual Studio 2012 для разработки приложения WP8. Я добавил ссылку на мой проект (Добавить ссылку на службу). Поэтому я могу использовать функции webservice.
client = new YChatWebService.WebServiceControllerPortTypeClient();
client.loginCompleted += client_loginCompleted; // this.token = e.Result;
client.loginAsync(this.username, this.password);
client.getTestCompleted += client_getTestCompleted;
client.getTestAsync(this.token);
function getTestAsync
и loginAsync
return void
, и оба они асинхронны. Возможно ли вернуть функции Task<T>
? Я хотел бы использовать ключевые слова async
/await
в моей программе.
Ответ:
Благодарим вас за помощь.
Кажется, что работает следующий код.
internal static class Extension
{
private static void TransferCompletion<T>(
TaskCompletionSource<T> tcs, System.ComponentModel.AsyncCompletedEventArgs e,
Func<T> getResult)
{
if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(getResult());
}
}
public static Task<loginCompletedEventArgs> LoginAsyncTask(this YChatWebService.WebServiceControllerPortTypeClient client, string userName, string password)
{
var tcs = new TaskCompletionSource<loginCompletedEventArgs>();
client.loginCompleted += (s, e) => TransferCompletion(tcs, e, () => e);
client.loginAsync(userName, password);
return tcs.Task;
}
}
Я называю это таким образом
client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask(this.username, this.password);
Ответы
Ответ 1
Предполагая, что loginAsync возвращает void, а событие loginCmpleted запускается при входе в систему, это называется асинхронным шаблоном на основе событий или EAP.
Чтобы преобразовать EAP в await/async, обратитесь к Задачи и Асинхронный шаблон на основе событий. В частности, вы захотите использовать TaskCompletionSource для преобразования модели на основе событий в целевую модель. Когда у вас есть модель на основе задач, вы можете использовать функцию ожидания С# 5.
Вот пример:
// Use LoginCompletedEventArgs, or whatever type you need out of the .loginCompleted event
// This is an extension method, and needs to be placed in a static class.
public static Task<LoginCompletedEventArgs> LoginAsyncTask(this YChatWebService.WebServiceControllerPortTypeClient client, string userName, string password)
{
var tcs = CreateSource<LoginCompletedEventArgs>(null);
client.loginCompleted += (sender, e) => TransferCompletion(tcs, e, () => e, null);
client.loginAsync(userName, password);
return tcs.Task;
}
private static TaskCompletionSource<T> CreateSource<T>(object state)
{
return new TaskCompletionSource<T>(
state, TaskCreationOptions.None);
}
private static void TransferCompletion<T>(
TaskCompletionSource<T> tcs, AsyncCompletedEventArgs e,
Func<T> getResult, Action unregisterHandler)
{
if (e.UserState == tcs)
{
if (e.Cancelled) tcs.TrySetCanceled();
else if (e.Error != null) tcs.TrySetException(e.Error);
else tcs.TrySetResult(getResult());
if (unregisterHandler != null) unregisterHandler();
}
}
Теперь, когда вы преобразовали модель асинхронного программирования на основе событий на основе задачи, вы можете теперь использовать ожидание:
var client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask("myUserName", "myPassword");
Ответ 2
При добавлении ссылки на службу убедитесь, что вы выбрали Generate Task based operations
в разделе Advanced
. это создаст ожидаемые методы, такие как LoginAsync
возврат Task<string>
Ответ 3
Мне приходилось делать это несколько раз за последний год, и я использовал код @Judah выше и оригинальный пример он ссылается, но каждый раз, когда я сталкивался со следующей проблемой с обоими: асинхронный вызов работает, но не завершается. Если я пройду через него, я увижу, что он войдет в метод TransferCompletion
, но e.UserState == tcs
всегда будет false
.
Оказывается, что асинхронные методы веб-службы, такие как OP loginAsync
, имеют две подписи. Второй принимает параметр userState
. Решение состоит в том, чтобы передать объект TaskCompletionSource<T>
, созданный вами в качестве этого параметра. Таким образом, e.UserState == tcs
вернет true.
В OP, e.UserState == tcs
был удален, чтобы сделать работу кода понятной - я тоже искушался. Но я считаю, что это необходимо для обеспечения правильного завершения события.
Полный код:
public static Task<LoginCompletedEventArgs> RaiseInvoiceAsync(this Client client, string userName, string password)
{
var tcs = CreateSource<LoginCompletedEventArgs>();
LoginCompletedEventHandler handler = null;
handler = (sender, e) => TransferCompletion(tcs, e, () => e, () => client.LoginCompleted -= handler);
client.LoginCompleted += handler;
try
{
client.LoginAsync(userName, password, tcs);
}
catch
{
client.LoginCompleted -= handler;
tcs.TrySetCanceled();
throw;
}
return tcs.Task;
}
В качестве альтернативы, я считаю, что есть свойство tcs.Task.AsyncState
, которое предоставит userState
. Таким образом, вы можете сделать что-то вроде:
if (e.UserState == taskCompletionSource || e.UserState == taskCompletionSource?.Task.AsyncState)
{
if (e.Cancelled) taskCompletionSource.TrySetCanceled();
else if (e.Error != null) taskCompletionSource.TrySetException(e.Error);
else taskCompletionSource.TrySetResult(getResult());
unregisterHandler();
}
Это то, что я попытался изначально, поскольку это казалось более легким подходом, и я мог бы передать Guid, а не весь объект TaskCompletionSource. Стивен Клири имеет хорошую запись AsyncState, если вам интересно.
Ответ 4
Если вы хотите быть в состоянии ждать методов, они должны вернуть Task. Вы не можете ждать метода, который возвращает void. Если вы хотите, чтобы они возвращали значение, например int, они должны возвращать Task<int>
, тогда метод должен возвращать int.
public async Task loginAsync(string username, string password) {}
Затем вы можете вызвать
Task t = loginAsync(username, password);
//login executing
//do something while waiting
await t; //wait for login to complete