0

我正忙于一个当然使用silverlight 的windows phone 应用程序。这意味着调用任何 web 服务都必须异步完成,并且由于这对于防止整个应用程序在等待资源时挂起的最佳实践来说都是很好的,所以我仍然停留在“同步思维方式”中。 ..

因为我现在看到的方式是你最终有两种方法需要处理一个函数,例如:

1)实际调用webservice的方法:

public void myAsyncWebService(DownloadStringCompletedEventHandler callback)
{
    //Url to webservice
    string servletUrl = "https://deangrobler.com/someService/etc/etc"

    //Calls Servlet
    WebClient client = new WebClient();
    client.DownloadStringCompleted += callback;
    client.DownloadStringAsync(new Uri(servletUrl, UriKind.Absolute));
}

2) 以及在数据最终返回时处理数据的方法:

private void serviceReturn(object sender, DownloadStringCompletedEventArgs e)
{
    var jsonResponse = e.Result;
    //and so on and so forth...
}

因此,不必只创建和调用一个进入 Web 服务的方法,而是获取返回的结果并将其发送回给我,如下所示:

public string mySyncWebService(){
    //Calls the webservice
    // ...waits for return
    //And returns result
}

我必须在一个类中调用 myAsyncWebService,并在调用类中 创建另一个方法来处理 myAsyncWebService 返回的结果。在我看来,只是创建了混乱的代码。使用同步调用,您可以只调用一种方法并完成它。

我只是使用错误的异步调用吗?我的理解错了吗?我需要一些启发,我讨厌做这种混乱的异步调用。它使我的代码过于复杂,可读性简直……地狱。

感谢任何愿意改变我想法的人!

4

5 回答 5

2

你必须彻底改变你的想法才能异步编程。我根据经验说话。:)

我只是使用错误的异步调用吗?我的理解错了吗?

不。异步代码很难编写(不要忘记错误处理)并且极难维护。

这就是发明的async原因await

如果您能够升级到 VS2012,那么您可以使用Microsoft.Bcl.Async(目前处于测试阶段)来编写您的代码,如下所示:

string url1 = "https://deangrobler.com/someService/etc/etc";
string jsonResponse1 = await new WebClient().DownloadStringTaskAsync(url1);

string url2 = GetUriFromJson(jsonResponse1);
string jsonResponse2 = await new WebClient().DownloadStringTaskAsync(url2);

容易写。易于维护。

于 2012-11-21T13:28:35.560 回答
1

异步就像当您拨打电话并获得答录机时,如果您想要回电,请留下您的号码。第一种方法是您要求数据的调用,第二种方法是您为返回调用留下的“号码”。

于 2012-11-21T11:21:18.017 回答
1

使用同步调用,您可以只调用一种方法并完成它。

当然可以,但是如果您从 UI 线程执行此操作,您将阻塞整个 UI。这在任何现代应用程序中都是不可接受的,尤其是在浏览器或手机中运行的 Silverlight 应用程序中。任何人都不想使用在 DNS 查找超时时 30 秒无响应的电话。

所以在 UI 线程上,可能是因为用户在 UI 中做了一些动作,你开始了一个异步调用。当调用完成时,在后台线程上调用一个方法来处理调用的结果。此方法很可能会使用异步调用的结果更新 UI。

随着 .NET 4.5 中 async 和 await 的引入,可以简化一些“拆分”代码。幸运的是,async 和 await 现在可用于 Windows Phone 7.5 的 beta 版本,使用 NuGet 包Microsoft.Bcl.Async

这是一个小(有点傻)的例子,展示了如何使用异步链接两个 Web 服务调用。这适用于 .NET 4.5,但使用上面链接的 NuGet 包,您应该能够在 Windows Phone 7.5 上执行类似的操作。

async Task<String> GetCurrencyCode() {
  using (var webClient = new WebClient()) {
    var xml = await webClient.DownloadStringTaskAsync("http://freegeoip.net/xml/");
    var xElement = XElement.Parse(xml);
    var countryName = (String) xElement.Element("CountryName");
    return await GetCurrencyCodeForCountry(countryName);
  }
}

async Task<String> GetCurrencyCodeForCountry(String countryName) {
  using (var webClient = new WebClient()) {
    var outerXml = await webClient.DownloadStringTaskAsync("http://www.webservicex.net/country.asmx/GetCurrencyByCountry?CountryName=" + countryName);
    var outerXElement = XElement.Parse(outerXml);
    var innerXml = (String) outerXElement;
    var innerXElement = XElement.Parse(innerXml);
    var currencyCode = (String) innerXElement.Element("Table").Element("CurrencyCode");
    return currencyCode;
  }
}

但是,您仍然需要在 UI 线程和 async 之间架起一座桥梁GetCurrencyCode。您不能在事件处理程序中等待,但可以Task.ContinueWith在异步调用返回的任务上使用:

void OnUserAction() {
  GetCurrencyCode().ContinueWith(GetCurrencyCodeCallback);
}

void GetCurrencyCodeCallback(Task<String> task) {
  if (!task.IsFaulted)
    Console.WriteLine(task.Result);
  else
    Console.WriteLine(task.Exception);
}
于 2012-11-21T11:22:06.017 回答
1

如果您改用 lambda,这一切都会变得更加容易和可读。这也使您能够访问在“父”方法中声明的变量,如下例所示:

private void CallWebService()
{
    //Defined outside the callback
    var someFlag = true;

    var client = new WebClient();
    client.DownloadStringCompleted += (s, e) =>
    {
        //Using lambdas, we can access variables defined outside the callback
        if (someFlag)
        {
            //Do stuff with the result. 
        }
    };

    client.DownloadStringAsync(new Uri("http://www.microsoft.com/"));
}


编辑:这是另一个带有两个链式服务调用的示例。它仍然不是很漂亮,但恕我直言,它比 OPs 原始代码更具可读性。

private void CallTwoWebServices()
{
    var client = new WebClient();
    client.DownloadStringCompleted += (s, e) =>
    {
        //1st call completed. Now make 2nd call.
        var client2 = new WebClient();
        client2.DownloadStringCompleted += (s2, e2) =>
        {
            //Both calls completed.
        };
        client2.DownloadStringAsync(new Uri("http://www.google.com/"));
    };
    client.DownloadStringAsync(new Uri("http://www.microsoft.com/"));
}
于 2012-11-21T11:26:45.680 回答
1

为了避免创建混乱的代码,如果您因为使用较旧的框架而无法使用async / await模式,您会在他们的Caliburn Micr o 实现中找到有用的检查CoRoutines。使用这种模式,您可以创建一个可枚举的 yield 在每一轮执行一个新的异步段:从读者的角度来看,异步步骤显示为一个序列,但是在步骤之间行走(因此产生下一个)是通过异步等待外部完成的单任务。这是一个很好的模式,易于实现并且非常清晰易读。顺便说一句,如果你不想使用 Caliburn Micro 作为你的 MVVM 工具,因为你正在使用其他东西,你可以只使用协程工具,它在框架内是非常绝缘的。

让我从这篇博文中的一个示例中发布一些代码。

public IEnumerable<IResult> Login(string username, string password)
{
    _credential.Username = username;
    _credential.Password = password;

    var result = new Result();
    var request = new GetUserSettings(username);

    yield return new ProcessQuery(request, result, "Logging In...");

    if (result.HasErrors)
    {
        yield return new ShowMessageBox("The username or password provided is incorrect.", "Access Denied");
        yield break;
    }

    var response = result.GetResponse(request);

    if(response.Permissions == null || response.Permissions.Count < 1)
    {
        yield return new ShowMessageBox("You do not have permission to access the dashboard.", "Access Denied");
        yield break;
    }

    _context.Permissions = response.Permissions;

    yield return new OpenWith<IShell, IDashboard>();
}

不是很容易阅读吗?但它实际上是异步的:每个yield步骤都以异步方式执行,yield并且在前一个任务完成后的语句之后再次执行流程。

于 2012-11-21T11:53:34.220 回答