Async正在成为背后的痛苦,因为我正在尝试创建一个不易吸引的可重用库

Jim*_*988 2 c# api asynchronous windows-phone-8

因为对API的Post请求需要在Windows Phone上异步运行,所以我很难创建一个易于使用的精简库来与API进行交互.

问题是使用库的人总是需要提供回调函数.

我们来看看一些伪代码:

PostRequest类帮助我处理POST请求:

class PostRequest
{
    private Action<MemoryStream> Callback;

    public PostRequest(string urlPath, string data, Action<MemoryStream> callback)
    {
        Callback = callback;

        // Form the URI
        UriBuilder fullUri = new UriBuilder(urlPath);

        if (!string.IsNullOrEmpty(data))
            fullUri.Query = data;

        // Initialize a new WebRequest
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fullUri.Uri);
        request.Method = "POST";

        // Set up the state object for the async request
        DataUpdateState dataState = new DataUpdateState();
        dataState.AsyncRequest = request;

        // Start the asynchronous request
        request.BeginGetResponse(new AsyncCallback(HandleResponse),
            dataState);
    }

    private void HandleResponse(IAsyncResult asyncResult)
    {
        // Get the state information
        DataUpdateState dataState = (DataUpdateState)asyncResult.AsyncState;
        HttpWebRequest dataRequest = (HttpWebRequest)dataState.AsyncRequest;

        // End the async request
        dataState.AsyncResponse = (HttpWebResponse)dataRequest.EndGetResponse(asyncResult);
        if (dataState.AsyncResponse.StatusCode.ToString() == "OK")
        {
            // Create a stream from the response
            Stream response = dataState.AsyncResponse.GetResponseStream();
            TextReader textReader = new StreamReader(response, true);
            string jsonString = textReader.ReadToEnd();
            MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));

            // Send the stream through to the callback function
            Callback(stream);
        }
    }
}

public class DataUpdateState
{
    public HttpWebRequest AsyncRequest { get; set; }
    public HttpWebResponse AsyncResponse { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

API访问对象类:

class APIAuthenticationCredentials
{
    public String Username { get; set; }
    public String Password { get; set; }
}

class APIAO
{
    private String AuthUrl = "http://api.example.com/";
    public static Auth Auth = new Auth();

    //... 
    public static void Authenticate( String data, APIAuthenticationCredentials credentials, Action<MemoryStream> callback )
    {
        PostRequest request = new PostRequest(AuthURL, data, callback);   
    }
    //... 
}
Run Code Online (Sandbox Code Playgroud)

你会注意到我必须一直传递一个回调函数,这样一旦我的PostRequest类中的HandleResponse方法返回数据,数据就被转发到某个控制器上,使屏幕对数据做一些事情.目前,使用它并不是非常可怕:

private void DisplayData(MemoryStream stream)
{
    DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Auth));
    APIAO.Auth = (Auth)serializer.ReadObject(stream);
}

//...    
    APIAuthenticationCredentials credentials = new APIAuthenticationCredentials {
        Username = "whatever",
        Password = "whatever"
    }    
    APIAO.Authenticate( credentials, DisplayData );
//... 
Run Code Online (Sandbox Code Playgroud)

问题是我想创建某种存储库样式模式...假设API返回了不同的json模型,一个调用返回了一个产品数组...问题是我想创建一个可爱的存储库调用,例如:

IProductRepository productRepository = new ProductRepository();
productRepository.GetAll();
Run Code Online (Sandbox Code Playgroud)

但是我也必须在其中放置一些GOSH DARN回调函数,这意味着API返回的任何对象类型的每个存储库方法都将具有此MemoryStream回调...如果我想要更改该功能,我'我必须到处更新那些东西哟.:(有没有人看到更好的方式做这个废话.

这开始变得太复杂了

--crying

Ser*_*rvy 7

使用较新的语言结构的更简单的答案是:

public static Task<string> GetData(string url, string data)
{
    UriBuilder fullUri = new UriBuilder(url);

    if (!string.IsNullOrEmpty(data))
        fullUri.Query = data;

    WebClient client = new WebClient();
    client.Credentials = CredentialCache.DefaultCredentials;//TODO update as needed
    return client.DownloadStringTaskAsync(fullUri.Uri);
}
Run Code Online (Sandbox Code Playgroud)

在4.0项目中,您可以使用a TaskCompletionSource将非Task异步模型转换为Task:

public static Task<string> GetData2(string url, string data)
{
    UriBuilder fullUri = new UriBuilder(url);

    if (!string.IsNullOrEmpty(data))
        fullUri.Query = data;

    WebClient client = new WebClient();
    client.Credentials = CredentialCache.DefaultCredentials;//TODO update as needed

    var tcs = new TaskCompletionSource<string>();

    client.DownloadStringCompleted += (s, args) =>
    {
        if (args.Error != null)
            tcs.TrySetException(args.Error);
        else if (args.Cancelled)
            tcs.TrySetCanceled();
        else
            tcs.TrySetResult(args.Result);
    };

    client.DownloadStringAsync(fullUri.Uri);

    return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)

调用者现在有一个Task<string>表示此异步操作的结果.他们可以同步等待它并使用Result属性获得结果,他们可以添加一个将在操作完成使用时执行的回调ContinueWith,或者他们可以await在一个async方法中的任务,该方法在引擎盖下将连接该方法的其余部分作为该任务的延续,但没有创建新方法甚至是新范围,即

public static async Task Foo()
{
    string result = await GetData("http://google.com", "");
    Console.WriteLine(result);
}
Run Code Online (Sandbox Code Playgroud)

这将启动异步任务,添加的回调(或延续)到该任务,以便在运行时,它会继续执行代码离开的地方,在这一点上,然后将结果写到控制台和纪念Task这个方法返回完成后,将继续执行方法的任何延续(允许async方法的组合).

  • 您可以将"Microsoft.Bcl.Async"安装到WinPhone项目中,并获取"WebClient"的扩展方法.另一种方法是安装`Microsoft.Net.Http`并使用`HttpClient`代替(我建议使用`HttpClient`进行更新的开发). (4认同)
  • @usr为了进一步说明,它反转了回调如何应用于异步操作.异步对象只是定义了它自己的异步,而不是每个异步操作都需要事先知道所有它的回调,然后可以通过调用者添加回调(延续)来传递它们.那,它大大改善了对异常,取消等的支持. (3认同)
  • 只是为了澄清*为什么*这比回调更好:`Task`逻辑地打包正在完成的工作并允许`await`自动注册回调.回调仍然存在,但隐藏. (2认同)
  • Windows Phone项目没有.NET Framework定位.他们使用Silverlight的简化分发,因此上面的Bcl NuGet包是WP中异步Web请求处理的最佳选择.在线查找软件包时,您需要从NuGet的搜索下拉列表中选择**Include Prerelease**. (2认同)