通过调用 <asyncmethod>.Result 实现同步方法
Implementing a synchronous method with calling the <asyncmethod>.Result
我有这个异步方法:
public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
using (var client = new HttpClient())
{
HttpResponseMessage message = await client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"));
var readAsStringAsync = await message.Content.ReadAsStringAsync();
return await readAsStringAsync.FromJsonAsync<RES>(mySerializerSettings);
}
}
其中 FromJsonAsync
作为扩展方法实现:
public async static Task<T> FromJsonAsync<T>(this string data, JsonSerializerSettings settings) where T : new()
{
return (T)(await JsonConvert.DeserializeObjectAsync<T>(data, settings));
}
现在我想添加一个常规的同步 Post
方法,我认为实现是:
public RES Post<RES>(string url, string content) where RES : new()
{
return PostAsync<RES>(url, content).Result;
}
但这并不真的有效。我看到请求是通过 Http 嗅探器发送的,我得到了回复,但我在调试时卡住了,无法继续。
顺便说一句,这个 确实 有效(使用 Result
而不是 await
):
public RES Post<RES>(string url, string content) where RES : new()
{
using (var client = new HttpClient())
{
HttpResponseMessage message = client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json")).Result;
var readAsStringAsync = message.Content.ReadAsStringAsync().Result;
return readAsStringAsync.FromJson<RES>(mySerializerSettings);
}
}
其中 FromJson
作为扩展方法实现:
public static T FromJson<T>(this string data, JsonSerializerSettings settings) where T : new()
{
return (T)JsonConvert.DeserializeObject<T>(data, settings);
}
该应用程序是 Web 后端 (WebApi)。
我做错了什么?
你手上可能遇到了死锁。
Asp.net 使用 SynchronizationContext
到 post 延续回到请求上下文。如果上下文被阻塞(就像您在 PostAsync<RES>(url, content).Result
上的情况一样),则无法执行继续操作,因此异步方法无法完成,并且出现死锁。
您可以使用 ConfigureAwait(false)
:
来避免它
public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
using (var client = new HttpClient())
{
HttpResponseMessage message = await client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"));
var readAsStringAsync = await message.Content.ReadAsStringAsync().ConfigureAwait(false);
return await readAsStringAsync.FromJsonAsync<RES>(mySerializerSettings).ConfigureAwait(false);
}
}
但最好只避免在异步代码上同步阻塞,并为同步和异步设置两个不同的版本。
您似乎有一个非异步函数,并且您想要启动一个将调用 PostAsync 的任务并等待此任务完成和 return 任务的结果。这是你的问题吗?
- 要启动任务,请使用 Task.Run( () => ...);
- 等待任务使用Task.Wait(...);
- 查看任务是否因异常停止:Task.IsFaulted
- 任务结果在Task.Result
您的代码可以是:
public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
// start the task that will call PostAsync:
var postTask = Task.Run( () => PostAsync(url, content));
// while this task is running you can do other things
// once you need the result: wait for the task to finish:
postTask.Wait();
// If needed check Task.IsFaulted / Task.IsCanceled etc. to check for errors
// the returned value is in Task.Result:
return postTask.Result;
}
虽然可能,但我不会使用@i3arnon 提供的答案。通常,您不应阻塞异步代码。尽管 ConfigureAwait(false)
确实有效,但它可能会导致您的代码库混乱,其他开发人员也可能最终阻止使用 .Result
,而不使用 ConfigureAwait
或理解其含义。
相反,暴露真正同步的同步方法:
public RES Post<RES>(string url, string content) where RES : new()
{
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var result = client.UploadString(url, content);
return JsonConvert.DeserializeObject<RES>(result, jsonSerializerSettings);
}
}
我有这个异步方法:
public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
using (var client = new HttpClient())
{
HttpResponseMessage message = await client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"));
var readAsStringAsync = await message.Content.ReadAsStringAsync();
return await readAsStringAsync.FromJsonAsync<RES>(mySerializerSettings);
}
}
其中 FromJsonAsync
作为扩展方法实现:
public async static Task<T> FromJsonAsync<T>(this string data, JsonSerializerSettings settings) where T : new()
{
return (T)(await JsonConvert.DeserializeObjectAsync<T>(data, settings));
}
现在我想添加一个常规的同步 Post
方法,我认为实现是:
public RES Post<RES>(string url, string content) where RES : new()
{
return PostAsync<RES>(url, content).Result;
}
但这并不真的有效。我看到请求是通过 Http 嗅探器发送的,我得到了回复,但我在调试时卡住了,无法继续。
顺便说一句,这个 确实 有效(使用 Result
而不是 await
):
public RES Post<RES>(string url, string content) where RES : new()
{
using (var client = new HttpClient())
{
HttpResponseMessage message = client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json")).Result;
var readAsStringAsync = message.Content.ReadAsStringAsync().Result;
return readAsStringAsync.FromJson<RES>(mySerializerSettings);
}
}
其中 FromJson
作为扩展方法实现:
public static T FromJson<T>(this string data, JsonSerializerSettings settings) where T : new()
{
return (T)JsonConvert.DeserializeObject<T>(data, settings);
}
该应用程序是 Web 后端 (WebApi)。
我做错了什么?
你手上可能遇到了死锁。
Asp.net 使用 SynchronizationContext
到 post 延续回到请求上下文。如果上下文被阻塞(就像您在 PostAsync<RES>(url, content).Result
上的情况一样),则无法执行继续操作,因此异步方法无法完成,并且出现死锁。
您可以使用 ConfigureAwait(false)
:
public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
using (var client = new HttpClient())
{
HttpResponseMessage message = await client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"));
var readAsStringAsync = await message.Content.ReadAsStringAsync().ConfigureAwait(false);
return await readAsStringAsync.FromJsonAsync<RES>(mySerializerSettings).ConfigureAwait(false);
}
}
但最好只避免在异步代码上同步阻塞,并为同步和异步设置两个不同的版本。
您似乎有一个非异步函数,并且您想要启动一个将调用 PostAsync 的任务并等待此任务完成和 return 任务的结果。这是你的问题吗?
- 要启动任务,请使用 Task.Run( () => ...);
- 等待任务使用Task.Wait(...);
- 查看任务是否因异常停止:Task.IsFaulted
- 任务结果在Task.Result
您的代码可以是:
public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
// start the task that will call PostAsync:
var postTask = Task.Run( () => PostAsync(url, content));
// while this task is running you can do other things
// once you need the result: wait for the task to finish:
postTask.Wait();
// If needed check Task.IsFaulted / Task.IsCanceled etc. to check for errors
// the returned value is in Task.Result:
return postTask.Result;
}
虽然可能,但我不会使用@i3arnon 提供的答案。通常,您不应阻塞异步代码。尽管 ConfigureAwait(false)
确实有效,但它可能会导致您的代码库混乱,其他开发人员也可能最终阻止使用 .Result
,而不使用 ConfigureAwait
或理解其含义。
相反,暴露真正同步的同步方法:
public RES Post<RES>(string url, string content) where RES : new()
{
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var result = client.UploadString(url, content);
return JsonConvert.DeserializeObject<RES>(result, jsonSerializerSettings);
}
}