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

前端 未结 5 722
清酒与你
清酒与你 2020-12-14 07:08

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 Serv

相关标签:
5条回答
  • 2020-12-14 07:30

    (Copied from OP, per https://meta.stackexchange.com/a/150228/136378 )

    Answer:

    Following code seems to work.

    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;
        }
    }
    

    I call it this way

    client = new YChatWebService.WebServiceControllerPortTypeClient();
    var login = await client.LoginAsyncTask(this.username, this.password);
    
    0 讨论(0)
  • 2020-12-14 07:34

    If you want to be able to await the methods, they should return Task. You cannot await a method that returns void. If you want them to return a value, like int they should return Task<int> then the method should return int.

    public async Task loginAsync(string username, string password) {}
    

    Then you can call

    Task t = loginAsync(username, password);
    //login executing
    //do something while waiting
    
    await t; //wait for login to complete
    
    0 讨论(0)
  • 2020-12-14 07:37

    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>

    0 讨论(0)
  • 2020-12-14 07:40

    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.

    0 讨论(0)
  • 2020-12-14 07:42

    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");
    
    0 讨论(0)
提交回复
热议问题