I have a webservice written in Yii (php framework).
I use C# and Visual Studio 2012 to develop a WP8 application. I added a service reference to my project (Add Service Reference). So I am able to use webservice functions.
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
and loginAsync
return void
and both are asynchronous. Is it possible for the functions to return Task<T>
? I would like to use async
/await
keywords in my program.
To call a web service asynchronously, a client thread can use a WaitHandle. When you get to a place in your client code when you are required to wait for the service to end, you call a WaitHandle there. Listing 8-9 is an example of how to use the WaitHandle.
We add an async keyword to the method signature, modify the return type by using Task , and we use the await keyword when we call the GetAllCompanies awaitable method. The rest of the code – the mapping part, the logging part, and the return of the result – will be executed after the awaitable operation completes.
An alternative way to wait for all promises to be done, you have to use async and await keywords, just add async keyword before function checkPhotos(... and ads await keyword before Promise. all... , that's will tell js engine that you need to run the code below await line when you got the response.
The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains. Async functions may also be defined as expressions.
Assuming that loginAsync returns void, and loginCmpleted event fires when login is done, this is called the Event-based Asynchronous Pattern, or EAP.
To convert EAP to await/async, consult Tasks and the Event-based Asynchronous Pattern. In particular, you'll want to make use of the TaskCompletionSource to convert the event-based model to a Task-based model. Once you've got a Task-based model, you can use C# 5's sexy await feature.
Here's an example:
// 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();
}
}
Now that you've converted the Event-based async programming model to a Task-based one, you can now use await:
var client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask("myUserName", "myPassword");
I've had to do this a couple of times over the last year and I've used both @Judah's code above and the original example he has referenced but each time I've hit on the following problem with both: the async call works but doesn't complete. If I step through it I can see that it will enter the TransferCompletion
method but the e.UserState == tcs
will always be false
.
It turns out that web service async methods like the OP's loginAsync
have two signatures. The second accepts a userState
parameter. The solution is to pass the TaskCompletionSource<T>
object you created as this parameter. This way the e.UserState == tcs
will return true.
In the OP, the e.UserState == tcs
was removed to make the code work which is understandable - I was tempted too. But I believe this is there to ensure the correct event is completed.
The full code is:
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;
}
Alternatively, I believe there is a tcs.Task.AsyncState
property too that will provide the userState
. So you could do something like:
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();
}
This was what I tried initially as it seemed a lighter approach and I could pass a Guid rather than the full TaskCompletionSource object. Stephen Cleary has a good write-up of the AsyncState if you're interested.
While adding your service reference make sure you selected Generate Task based operations
in Advanced
section. this will create awaitable methods like LoginAsync
returning Task<string>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With