C# 如何使用 async/await 调用网络服务?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14103288/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 10:40:32  来源:igfitidea点击:

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

c#soapwindows-phone-8wsdl

提问by MPeli

I have a webservicewritten in Yii(php framework).

我有一个web服务写在Yii中(PHP框架)。

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.

我使用 C# 和 Visual Studio 2012 来开发 WP8 应用程序。我向我的项目添加了一个服务引用(添加服务引用)。所以我能够使用网络服务功能。

   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 getTestAsyncand loginAsyncreturn voidand both are asynchronous. Is it possible for the functions to return Task<T>? I would like to use async/awaitkeywords in my program.

functiongetTestAsyncloginAsyncreturnvoid都是异步的。函数是否有可能返回Task<T>?我想在我的程序中使用async/await关键字。

采纳答案by Judah Gabriel Himango

Assuming that loginAsync returns void, and loginCmpleted event fires when login is done, this is called the Event-based Asynchronous Pattern, or EAP.

假设 loginAsync 返回 void,并且 loginCmpled 事件在登录完成时触发,这称为基于事件的异步模式或 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.

要将 EAP 转换为 await/async,请参阅任务和基于事件的异步模式。特别是,您需要使用 TaskCompletionSource 将基于事件的模型转换为基于任务的模型。一旦你有了一个基于任务的模型,你就可以使用 C# 5 的性感等待功能。

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:

现在您已经将基于事件的异步编程模型转换为基于任务的异步编程模型,您现在可以使用 await:

var client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask("myUserName", "myPassword");

回答by I4V

While adding your service reference make sure you selected Generate Task based operationsin Advancedsection. this will create awaitable methods like LoginAsyncreturning Task<string>

在添加您的服务参考时,请确保您Generate Task based operationsAdvanced部分中选择。这将创建可等待的方法,如LoginAsync返回Task<string>

回答by Nick Bray

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.

如果您希望能够等待方法,它们应该返回 Task。您不能等待返回 void 的方法。如果您希望它们返回一个值,比如它们应该返回 int,Task<int>那么该方法应该返回 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

回答by Digbyswift

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 examplehe 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 TransferCompletionmethod but the e.UserState == tcswill always be false.

去年我不得不这样做几次,并且我使用了上面@Judah 的代码和他引用的原始示例,但每次我都遇到以下问题时:异步调用有效但没有完成。如果我逐步完成它,我可以看到它将进入该TransferCompletion方法,但e.UserState == tcs将始终是false.

It turns out that web service async methods like the OP's loginAsynchave two signatures. The second accepts a userStateparameter. The solution is to pass the TaskCompletionSource<T>object you created as this parameter. This way the e.UserState == tcswill return true.

事实证明,像 OP 这样的 Web 服务异步方法loginAsync有两个签名。第二个接受一个userState参数。解决方案是将TaskCompletionSource<T>您创建的对象作为此参数传递。这样,e.UserState == tcs将返回true。

In the OP, the e.UserState == tcswas 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.

在 OP 中,e.UserState == tcs删除了 以使代码工作是可以理解的 - 我也受到了诱惑。但我相信这是为了确保完成正确的事件。

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.AsyncStateproperty too that will provide the userState. So you could do something like:

或者,我相信也有一个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();
}

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 AsyncStateif you're interested.

这是我最初尝试的方法,因为它似乎是一种更轻松的方法,而且我可以传递 Guid 而不是完整的 TaskCompletionSource 对象。如果您有兴趣,Stephen Cleary 有一篇关于 AsyncState好文章

回答by Brian

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

(从 OP 复制,根据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);