通过调用 <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);
    }
}