30

我有一个用Yii(php 框架)编写的web服务。

我使用 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); 

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

4

5 回答 5

35

假设 loginAsync 返回 void,并且 loginCmpleted 事件在登录完成时触发,这称为基于事件的异步模式,或 EAP。

要将 EAP 转换为等待/异步,请参阅任务和基于事件的异步模式。特别是,您需要使用 TaskCompletionSource 将基于事件的模型转换为基于任务的模型。一旦你有了一个基于任务的模型,你就可以使用 C# 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();
    } 
}

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

var client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask("myUserName", "myPassword");
于 2012-12-31T15:42:20.163 回答
7

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

于 2012-12-31T15:28:52.630 回答
7

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

事实证明,像 OP 这样的 Web 服务异步方法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 对象。如果您有兴趣, Stephen Cleary对 AsyncState 有很好的描述。

于 2016-12-09T17:02:59.740 回答
0

(从 OP 复制,每https://meta.stackexchange.com/a/150228/136378

回答:

以下代码似乎有效。

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);
于 2019-11-27T15:12:30.940 回答
-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
于 2012-12-31T15:44:13.943 回答