Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use async/await to call a webservice?

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.

like image 461
MPeli Avatar asked Dec 31 '12 15:12

MPeli


People also ask

How do you call a web service asynchronously?

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.

How does async await work in Web API?

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.

How do I make async call wait?

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.

What is async await used for?

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.


3 Answers

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");
like image 173
Judah Gabriel Himango Avatar answered Oct 28 '22 16:10

Judah Gabriel Himango


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.

like image 35
Digbyswift Avatar answered Oct 28 '22 15:10

Digbyswift


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>

like image 7
I4V Avatar answered Oct 28 '22 17:10

I4V