从同步思维模式转变为异步思维模式

时间:2012-11-21 11:12:39

标签: c# web-services silverlight asynchronous synchronous

我正忙着使用当然使用silverlight的Windows手机应用程序。这意味着调用任何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...
}

因此,不必仅创建并调用转发到webservice的单个方法,而是获取返回的结果并将其发送回给我:

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

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

我刚刚使用异步调用错了吗?我的理解错了吗?我需要一些启发,我讨厌做这个凌乱的异步调用。它使我的代码太复杂,可读性只是去......地狱。

感谢任何愿意改变主意的人!

5 个答案:

答案 0 :(得分:2)

你必须把你的想法转变为异步编程。我是根据经验说的。 :)

  

我刚刚使用异步调用错了吗?我的理解错了吗?

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

这就是asyncawait被发明的原因。

如果您能够升级到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);

易于书写。易于维护。

答案 1 :(得分:1)

异步就像打电话和接听电话一样,如果你想要一个回电话就可以留下号码。第一种方法是要求提供数据的电话,第二种方法是您为返回电话留下的“号码”。

答案 2 :(得分:1)

  

使用同步调用,您只需调用一个方法即可完成。

当然,但是如果你从UI线程那样做,那么你将阻止整个UI。这在任何现代应用程序中都是不可接受的,特别是在浏览器或手机中运行的Silverlight应用程序中。在DNS查找超时的情况下30秒没有响应的电话不是任何人想要使用的。

因此在UI线程上,可能是因为用户在UI中执行了某些操作,您启动了异步调用。当调用完成时,在后台线程上调用一个方法来处理调用的结果。此方法很可能会使用异步调用的结果更新UI。

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

这是一个小的(有点愚蠢的)示例,演示了如何使用异步链接两个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线程和异步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);
}

答案 3 :(得分:1)

如果你使用lambdas,这一切都变得更容易和可读。这也使您能够访问在&#34; parent&#34;中声明的变量。方法,如下例所示:

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/"));
}


编辑:这是两个链式服务调用的另一个例子。它仍然不是很漂亮,但是它比OP原始代码更具可读性。

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/"));
}

答案 4 :(得分:1)

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

让我发布一些this blog post中的示例代码。

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语句后执行流程再次流动。