异步下载和反序列化 [英] Async Download and Deserialize

查看:123
本文介绍了异步下载和反序列化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

晚上好,

我尝试使用异步编程细化一些code和想知道如果下面code写得好与否,如果有什么办法改进它。

其目的是从给定的URL下载一些JSON和反序列化是一个对象。

我有三个(现有4个)的问题(,并在帖子的末尾描述1期


  • 我应该使用 TaskEx.Run 运行
    Newtonsoft.Json.JsonConvert.DeserializeObject< T>

  • 有没有什么好的方法(不检查对象的属性),以
    知道 rootObject 创建成功?

  • 我应该检查什么地方取消是否要求或不?

  • 新问题)应该做一个新的请求之前,我CancelPendingRequests?

事不宜迟,这里的code:

 内部静态类WebUtilities
{
    ///<总结>
    ///下载总数给定的URL的网页
    ///< /总结>
    ///< PARAM NAME =URL>链接直接来自以下的下载页面; /参数>
    ///< PARAM NAME =的CancellationToken>令牌取消下载< /参数>
    ///<返回>本页面内容及LT; /回报>
    内部静态异步任务<串GT; DownloadStringAsync(字符串URL,的CancellationToken的CancellationToken)
    {
        尝试
        {
            //创建HTTP客户端即使异常抛出的要求进行处置(与使用finally语句)
            使用(VAR的客户=新的HttpClient(){超时= TimeSpan.FromSeconds(5)})
            {
                //我就应该总是这样做呢?
                client.CancelPendingRequests();                //做请求,并且对其进行处理
                使用(VAR响应=等待client.GetAsync(URL,的CancellationToken).ConfigureAwait(假))
                {
                    //如果反应是成功的(否则将返回null)
                    如果(response.IsSuccessStatus code)
                    {
                        //返回其内容
                        返回等待response.Content.ReadAsStringAsync()ConfigureAwait(假)。
                    }
                }
            }
        }
        赶上(异常前)在(前为System.Net.Sockets.SocketException ||
                                    当然是InvalidOperationException异常||
                                    当然是OperationCanceledException ||
                                    当然是System.Net.Http.Htt prequestException)
        {
            的WriteLine(DownloadStringAsync任务已被取消。);
            的WriteLine(ex.Message);
            返回null;
        }        //返回null,如果反应是不成功
        返回null;
    }    ///<总结>
    ///下载从一个给定的URL JSON和尝试其反序列化到给定的引用类型(类)
    ///< /总结>
    ///< typeparam NAME =T>在类反序列化到< / typeparam>
    ///< PARAM NAME =URL>链接直接来自以下的下载JSON; /参数>
    ///< PARAM NAME =的CancellationToken>令牌取消下载< /参数>
    ///<返回>在反序列化对象< /回报>
    内部静态异步任务< T> DownloadJsonAndDeserialize< T>(字符串URL,的CancellationToken的CancellationToken)其中T:类,新的()
    {
        //从指定网址下载JSON
        VAR jsonResponse =等待DownloadStringAsync(URL,的CancellationToken).ConfigureAwait(假);        //如果响应是无效的,没有必要再进一步
        如果(string.IsNullOrEmpty(jsonResponse))
            //返回默认的构造函数实例
            返回新T();        //尝试反序列化
        尝试
        {
            // JSON数据反序列化给定的.NET对象
            //我应该使用TaskEx.Run在这里?
            返回Newtonsoft.Json.JsonConvert.DeserializeObject< T>(jsonResponse);
        }
        赶上(异常前)在(前为JsonException)
        {
            的WriteLine(反序列化JSON给定的引用类型出事了。);
            的WriteLine(ex.Message);
        }        //返回默认的构造函数实例
        返回新T();
    }
}

和调用code,人会做这样的事情:

 内部静态异步任务CallAsync()
    {
        RootObject根= NULL;        使用(VAR CTS =新CancellationTokenSource(TimeSpan.FromSeconds(10)))
        {
            VAR令牌= cts.Token;            根=等待WebUtilities.DownloadJsonAndDeserialize< RootObject>(URL,令牌).ConfigureAwait(假);
        }        //做一些rootObject
        //知道rootObject已成功创建,移动之前没有什么好的办法?
    }

你会改变什么?为什么?

谢谢!

编辑:


  • 将@codran建议 - 每个等待现在使用 ConfigureAwait(假)

  • CancellationTokenSource 现在处置(using语句)

  • 建议由@codran - 一些例外已被过滤

  • DownloadStringAsync 有一个的try-catch 使用语句$ C>
    阻止更多的可读性(而不是的try-catch-最后)。

  • 现在,检查是否响应 IsSuccessStatus code

发现问题(创造了另一个问题,它 - 超时在Xamarin HTTP请求):

有趣的是,当一个主机无法到达(如离线本地服务器),什么也没有发生 GetAsync 几分钟后(约3) System.Net.WebException 被抛出话说错误:ConnectFailure(连接超时)。内部异常是 System.Net.Sockets.SocketsException (完整日志在这里: http://pastebin.com/MzHyp2FM )。

我已经尝试设置 client.Timeout 5秒的但这似乎不工作

可能是一个错误Xamarin,虽然(<一个href=\"https://forums.xamarin.com/discussion/5941/system-net-http-httpclient-timeout-seems-to-be-ignored\" rel=\"nofollow\">https://forums.xamarin.com/discussion/5941/system-net-http-httpclient-timeout-seems-to-be-ignored).

无论哪种方式,不应该取消的CancellationToken后自动10秒

此超时问题发生与/时:


  • 脱机/不可达的IP地址(在我的情况下
    离线本地服务器,如192.168.1.101:8080)的要求(例如,GetAsync,SendAsync,GetResponseAsync)

的code与/运作良好时:


  • 没有Internet连接(抛出异常)

  • DNS无法解析URL(抛出异常)

  • 一个有效的URL(启动和运行)给出


  • 该请求的从桌面客户端(例如,WPF),其中如果做
    IP离线/可达它抛出异常4真快(
    可以作出连接,因为目标机器积极拒绝


结论


  • Xamarin似乎在这些请求的一些错误(与在超时
    至少?),因为它们不产生预期的结果,否则见于
    桌面应用程序。


解决方案

  

我应该使用 TaskEx.Run 运行 Newtonsoft.Json.JsonConvert.DeserializeObject&LT; T&GT;


投掷JSON deserialisation到另一个线程可能不会有什么成果。你释放当前线程但需要等待对你的工作在另一个线程调度。除非你需要Newtonsoft.Json的API,可考虑使用<一个href=\"https://msdn.microsoft.com/en-us/library/system.net.http.httpcontentextensions.readasasync(v=vs.118).aspx#M:System.Net.Http.HttpContentExtensions.ReadAsAsync%60%601%28System.Net.Http.HttpContent%29\"相对=nofollow> ReadAsAsync&LT; T&GT; 。您可以使用一个<一个href=\"https://msdn.microsoft.com/en-us/library/system.net.http.formatting.jsonmediatypeformatter(v=vs.118).aspx\"相对=nofollow> JsonMediaTypeFormatter ,该<一个href=\"https://aspnetwebstack.$c$cplex.com/SourceControl/latest#src/System.Net.Http.Formatting/Formatting/JsonMediaTypeFormatter.cs\"相对=nofollow>使用Newtonsoft.Json内部反正。


  

有没有什么好的方法(不检查对象属性)知道,如果rootObject创建成功?


您需要定义什么成功的样子。例如,您的JSON可能是,所以 rootObject 为空。或者它可能是缺失的属性,或者你的JSON有没有deserialised额外的属性。我不认为有一种方法可以失败过剩或缺失的属性序列化,所以你必须检查自己。我可能是错的这一点。


  

我应该检查什么地方取消是否要求或不?


您需要看到什么有意义在code和当它被取消。例如,有作用于取消标记为你的函数最后一行,当所有的工作已经完成,没有任何意义。

是否有意义您的应用程序取消操作如果JSON已经下载,但尚未deserialised?是否有意义您的应用程序取消操作如果JSON已经deserialised,但对象图还没有得到验证(问题2)?

这真的取决于你的需要,但我可能会取消,如果下载尚未完成 - 这似乎你已经这样做 - 但一旦下载完成,那就是不归路


  

你会改变什么?为什么?


使用在您的await 任务ConfigureAwait(假)。这prevents死锁,如果你的code有史以来块,并等待结果的任务(如 .Wait()。结果)。

使用使用块而不是尝试 - 最后

Good evening,

I am trying to refine some code using async programming and would like to know if the following code is well written or not, and if is there any way to improve it.

Its purpose is to download some JSON from a given URL and deserialize it to an object.

I have three (now four) questions (and 1 issue described at the end of the post):

  • Should I use TaskEx.Run to run Newtonsoft.Json.JsonConvert.DeserializeObject<T> ?
  • Is there any good way (without checking the object properties) to know if rootObject was successfully created?
  • Should I check somewhere whether Cancellation was requested or not?
  • (new question) Should I CancelPendingRequests before doing a new request?

Without further ado, here's the code:

internal static class WebUtilities
{
    /// <summary>
    /// Downloads the page of the given url
    /// </summary>
    /// <param name="url">url to download the page from</param>
    /// <param name="cancellationToken">token to cancel the download</param>
    /// <returns>the page content</returns>
    internal static async Task<string> DownloadStringAsync(string url, CancellationToken cancellationToken)
    {
        try
        {
            // create Http Client and dispose of it even if exceptions are thrown (same as using finally statement)
            using (var client = new HttpClient() {Timeout = TimeSpan.FromSeconds(5)})
            {
                // should I always do this?
                client.CancelPendingRequests();

                // do request and dispose of it when done
                using (var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false))
                {
                    // if response was successful (otherwise it will return null)
                    if (response.IsSuccessStatusCode)
                    {
                        // return its content
                        return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                    }
                }
            }
        }
        catch (Exception ex) when (ex is System.Net.Sockets.SocketException || 
                                    ex is InvalidOperationException || 
                                    ex is OperationCanceledException ||
                                    ex is System.Net.Http.HttpRequestException)
        {
            WriteLine("DownloadStringAsync task has been cancelled.");
            WriteLine(ex.Message);
            return null;
        }

        // return null if response was unsuccessful
        return null;
    }

    /// <summary>
    /// Downloads Json from a given url and attempts its deserialization to a given reference type (class)
    /// </summary>
    /// <typeparam name="T">the class to deserialize to</typeparam>
    /// <param name="url">url to download the json from</param>
    /// <param name="cancellationToken">token to cancel the download</param>
    /// <returns>the deserialized object</returns>
    internal static async Task<T> DownloadJsonAndDeserialize<T>(string url, CancellationToken cancellationToken) where T : class, new()
    {
        // download json from the given url
        var jsonResponse = await DownloadStringAsync(url, cancellationToken).ConfigureAwait(false);

        // if the response is invalid, no need to go further
        if (string.IsNullOrEmpty(jsonResponse))
            // return a default constructor instance
            return new T();

        // try to deserialize
        try
        {
            // Deserialize json data to the given .NET object
            // Should I use TaskEx.Run here?
            return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonResponse);
        }
        catch (Exception ex) when (ex is JsonException)
        {
            WriteLine("Something went wrong while deserializing json to the given reference type.");
            WriteLine(ex.Message);
        }

        // return a default constructor instance
        return new T();
    }
}

And to call the code, one would do something like:

internal static async Task CallAsync()
    {
        RootObject root = null;

        using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
        {
            var token = cts.Token;

            root = await WebUtilities.DownloadJsonAndDeserialize<RootObject>(URL, token).ConfigureAwait(false);
        }

        // do something with rootObject
        // any good way to know if rootObject was successfully created, before moving on?
    }

What would you change? and why?

Thanks!

Edit:

  • Suggested by @codran - Every await is now using ConfigureAwait(false)
  • CancellationTokenSource is now disposed of (using statement)
  • Suggested by @codran - Some exceptions are now filtered
  • DownloadStringAsync has now 2 using statements inside a try-catch block for more readability (instead of a try-catch-finally).
  • Now checking if response IsSuccessStatusCode

Issue found (created another question for it - Timeouts in Xamarin HTTP requests):

Interestingly enough, when a host cannot be reached (e.g., an offline local server), nothing happens after GetAsync. After several minutes (around 3) a System.Net.WebException is thrown saying Error: ConnectFailure (Connection timed out). The Inner exception is System.Net.Sockets.SocketsException (full log here: http://pastebin.com/MzHyp2FM).

I have tried to set client.Timeout to 5 seconds but that doesn't seem to work.

Could be a Xamarin bug, though (https://forums.xamarin.com/discussion/5941/system-net-http-httpclient-timeout-seems-to-be-ignored).

Either way, shouldn't cancellationToken cancel automatically after 10 seconds?

This timeout issue happens with/when:

  • An offline/unreachable IP address (in my case an offline local server such as 192.168.1.101:8080) is requested (e.g., GetAsync, SendAsync, GetResponseAsync)

The code works well with/when:

  • There is no Internet Connection (throws an exception)
  • DNS cannot resolve the URL (throws an exception)
  • A valid URL (up and running) is given

  • The request is made from a desktop client (e.g., WPF), where if the IP is offline/unreachable it throws 4 exceptions really fast (No connection could be made because the target machine actively refused it)

Conclusions

  • Xamarin seems to have some bugs in these requests (with Timeouts at least?), as they do not give the expected results otherwise seen in desktop applications.

解决方案

Should I use TaskEx.Run to run Newtonsoft.Json.JsonConvert.DeserializeObject<T>?

Throwing the JSON deserialisation onto another thread likely won't achieve anything. You're freeing up the current thread but then need to wait for your work to be scheduled on another thread. Unless you need Newtonsoft.Json APIs specifically, consider using ReadAsAsync<T>. You can use that with a JsonMediaTypeFormatter, which uses Newtonsoft.Json internally anyway.

Is there any good way (without checking the object properties) to know if rootObject was successfully created?

You need to define what success looks like. For example, your JSON could be null, so rootObject is null. Or it could be missing properties, or your JSON has extra properties that were not deserialised. I don't think there's a way to fail the serialisation for excess or missing properties, so you would have to check that yourself. I could be wrong on this point.

Should I check somewhere whether Cancellation was requested or not?

You need to see what makes sense in your code and when it is being cancelled. For example, there's no point acting on the cancellation token as the very last line in your function, when all the work has been completed.

Does it make sense for your application to cancel the operation if the JSON has been downloaded, but not yet deserialised? Does it make sense for your application to cancel the operation if the JSON has been deserialised, but the object graph has not yet been validation (question 2)?

It really depends on your needs, but I'd probably cancel if the download has not yet completed - which it seems that you are already doing - but once the download is completed, that is the point of no return.

What would you change? and why?

Use ConfigureAwait(false) on the Tasks that you await. This prevents deadlocks if your code ever blocks and waits for the resulting Task (e.g. .Wait() or .Result).

Use using blocks instead of try-finally.

这篇关于异步下载和反序列化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆