晚上好,
我正在尝试使用异步编程来优化一些代码,并且想知道以下代码是否编写良好,以及是否有任何改进方法。
其目的是从给定的URL下载一些JSON并将其反序列化为对象。
我有三个(现在四个)问题(和帖子末尾描述的1个问题):
TaskEx.Run
来运行
Newtonsoft.Json.JsonConvert.DeserializeObject<T>
?rootObject
是否成功创建?不用多说,这里是代码:
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();
}
}
要调用代码,可以执行以下操作:
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?
}
你会改变什么?为什么?
谢谢!
修改
await
现在都使用ConfigureAwait(false)
CancellationTokenSource
现已被处理(使用声明)DownloadStringAsync
现在using
内有2 try-catch
个语句
阻止更多可读性(而不是try-catch-finally
)。IsSuccessStatusCode
发现问题(为其创建了另一个问题 - Timeouts in Xamarin HTTP requests):
有趣的是,当无法访问主机时(例如,离线本地服务器),GetAsync
之后没有任何反应。 几分钟后(大约3分钟)会抛出System.Net.WebException
,说错误:ConnectFailure(连接超时)。内部异常为System.Net.Sockets.SocketsException
(此处为完整日志:http://pastebin.com/MzHyp2FM)。
我尝试将client.Timeout
设置为5秒,但这似乎无法正常工作。
可能是一个Xamarin错误(https://forums.xamarin.com/discussion/5941/system-net-http-httpclient-timeout-seems-to-be-ignored)。
无论哪种方式,不应该在10秒后自动取消?
此超时问题发生在/ when:
该代码适用于/何时:
提供了有效的URL(启动并运行)
请求来自桌面客户端(例如,WPF),如果 IP处于脱机/无法访问状态,它会非常快速地抛出4个异常( No 可以建立连接,因为目标机器主动拒绝 它)
结论
答案 0 :(得分:2)
我应该使用
TaskEx.Run
来运行Newtonsoft.Json.JsonConvert.DeserializeObject<T>
吗?
将JSON反序列化抛到另一个线程上可能无法实现任何目标。你正在释放当前的线程,但是需要等待你的工作安排在另一个线程上。除非您特别需要Newtonsoft.Json API,否则请考虑使用ReadAsAsync<T>
。您可以使用JsonMediaTypeFormatter
,uses Newtonsoft.Json internally anyway。
有没有好的方法(不检查对象属性)知道rootObject是否已成功创建?
您需要定义成功的样子。例如,您的JSON可能是null
,因此rootObject
为空。或者它可能缺少属性,或者您的JSON具有未反序列化的额外属性。我不认为有一种方法可以使序列化过多或缺少属性,所以你必须自己检查一下。在这一点上我可能是错的。
我应该检查某处是否要求取消?
您需要查看代码中有意义的内容以及何时取消代码。例如,当所有工作都已完成时,作为函数中最后一行的取消令牌没有任何意义。
如果已下载JSON但尚未反序列化,您的应用程序是否有理由取消操作?如果JSON已被反序列化,但是对象图尚未经过验证(问题2),您的应用程序是否有理由取消操作?
这实际上取决于你的需求,但如果下载还没有完成,我可能会取消 - 这似乎你已经在做 - 但是一旦下载完成,那就是不归路。
你会改变什么?为什么?
在ConfigureAwait(false)
的任务上使用await
。如果代码阻塞并等待生成的任务(例如.Wait()
或.Result
),这可以防止死锁。
使用using
块代替try
- finally
。
答案 1 :(得分:0)
我在生产中使用以下代码而没有任何问题。
// Creating and disposing HttpClient is unnecessary
// if you are going to use it multiple time
// reusing HttpClient improves performance !!
// do not worry about memory leak, HttpClient
// is designed to close resources used in single "SendAsync"
// method call
private static HttpClient client;
public Task<T> DownloadAs<T>(string url){
// I know I am firing this on another thread
// to keep UI free from any smallest task like
// preparing httpclient, setting headers
// checking for result or anything..., why do that on
// UI Thread?
// this helps us using excessive logging required for
// debugging and diagnostics
return Task.Run(async ()=> {
// following parses url and makes sure
// it is a valid url
// there is no need for this to be done on
// UI thread
Uri uri = new Uri(url);
HttpRequestMessage request =
new HttpRequestMessage(uri,HttpMethod.Get);
// do some checks, set some headers...
// secrete code !!!
var response = await client.SendAsync(request,
HttpCompletionOption.ReadHeaders);
string content = await response.Content.ReadAsStringAsync();
if(((int)response.StatusCode)>300){
// it is good to receive error text details
// not just reason phrase
throw new InvalidOperationException(response.ReasonPhrase
+ "\r\n" + content);
}
return JsonConvert.DeserializeObject<T>(content);
});
}
ConfigureAwait很棘手,使调试更加复杂。此代码将毫无问题地运行。
如果您想使用CancellationToken
,则可以使用client.GetAsync
方法传递令牌。