编辑:这个问题看起来可能是同样的问题,但没有回答…

编辑:在测试用例5中,任务似乎停留在WaitingForActivation状态。

我在。net 4.5中使用System.Net.Http.HttpClient时遇到过一些奇怪的行为——“等待”调用(例如httpClient.GetAsync(…))的结果将永远不会返回。

这只发生在使用新的async/await语言功能和任务API时的特定情况下-当只使用延续时,代码似乎总是工作。

下面是一些重现这个问题的代码——将其放入Visual Studio 11中的一个新的“MVC 4 WebApi项目”中,以公开以下GET端点:

/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6

这里的每个端点返回相同的数据(来自stackoverflow.com的响应头),除了/api/test5,它永远不会完成。

我是否在HttpClient类中遇到了错误,或者我是否以某种方式误用了API ?

复制代码:

public class BaseApiController : ApiController
{
    /// <summary>
    /// Retrieves data using continuations
    /// </summary>
    protected Task<string> Continuations_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
    }

    /// <summary>
    /// Retrieves data using async/await
    /// </summary>
    protected async Task<string> AsyncAwait_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return result.Content.Headers.ToString();
    }
}

public class Test1Controller : BaseApiController
{
    /// <summary>
    /// Handles task using Async/Await
    /// </summary>
    public async Task<string> Get()
    {
        var data = await Continuations_GetSomeDataAsync();

        return data;
    }
}

public class Test2Controller : BaseApiController
{
    /// <summary>
    /// Handles task by blocking the thread until the task completes
    /// </summary>
    public string Get()
    {
        var task = Continuations_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test3Controller : BaseApiController
{
    /// <summary>
    /// Passes the task back to the controller host
    /// </summary>
    public Task<string> Get()
    {
        return Continuations_GetSomeDataAsync();
    }
}

public class Test4Controller : BaseApiController
{
    /// <summary>
    /// Handles task using Async/Await
    /// </summary>
    public async Task<string> Get()
    {
        var data = await AsyncAwait_GetSomeDataAsync();

        return data;
    }
}

public class Test5Controller : BaseApiController
{
    /// <summary>
    /// Handles task by blocking the thread until the task completes
    /// </summary>
    public string Get()
    {
        var task = AsyncAwait_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test6Controller : BaseApiController
{
    /// <summary>
    /// Passes the task back to the controller host
    /// </summary>
    public Task<string> Get()
    {
        return AsyncAwait_GetSomeDataAsync();
    }
}

当前回答

编辑:一般来说,尽量避免做下面的事情,除非是为了避免死锁而做的最后努力。阅读Stephen Cleary的第一条评论。

从这里开始快速修复。而不是写:

Task tsk = AsyncOperation();
tsk.Wait();

Try:

Task.Run(() => AsyncOperation()).Wait();

或者如果你需要一个结果:

var result = Task.Run(() => AsyncOperation()).Result;

从源代码(经过编辑以匹配上面的示例):

AsyncOperation现在将在线程池上调用 将不会是一个SynchronizationContext,以及内部使用的延续 AsyncOperation不会被强制返回到调用线程。

对我来说,这看起来是一个有用的选项,因为我没有选择使它异步的所有方式(这是我更喜欢的)。

来源:

Ensure that the await in the FooAsync method doesn’t find a context to marshal back to. The simplest way to do that is to invoke the asynchronous work from the ThreadPool, such as by wrapping the invocation in a Task.Run, e.g. int Sync() { return Task.Run(() => Library.FooAsync()).Result; } FooAsync will now be invoked on the ThreadPool, where there won’t be a SynchronizationContext, and the continuations used inside of FooAsync won’t be forced back to the thread that’s invoking Sync().

其他回答

这两个学派并不是真正的排斥。

这里是您必须使用的场景

   Task.Run(() => AsyncOperation()).Wait(); 

或者类似的东西

   AsyncContext.Run(AsyncOperation);

我有一个MVC操作,是在数据库事务属性下。这个想法(可能)是在发生错误时回滚操作中所做的所有事情。这不允许上下文切换,否则事务回滚或提交本身就会失败。

我需要的库是异步的,因为它预计运行异步。

这是唯一的选择。运行它作为一个正常的同步调用。

我只是说,各有千秋。

由于您使用的是. result或. wait或await,这将导致代码死锁。

你可以在异步方法中使用ConfigureAwait(false)来防止死锁

是这样的:

var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
                             .ConfigureAwait(false);

你可以在任何可能的地方使用ConfigureAwait(false)来不阻塞异步代码。

编辑:一般来说,尽量避免做下面的事情,除非是为了避免死锁而做的最后努力。阅读Stephen Cleary的第一条评论。

从这里开始快速修复。而不是写:

Task tsk = AsyncOperation();
tsk.Wait();

Try:

Task.Run(() => AsyncOperation()).Wait();

或者如果你需要一个结果:

var result = Task.Run(() => AsyncOperation()).Result;

从源代码(经过编辑以匹配上面的示例):

AsyncOperation现在将在线程池上调用 将不会是一个SynchronizationContext,以及内部使用的延续 AsyncOperation不会被强制返回到调用线程。

对我来说,这看起来是一个有用的选项,因为我没有选择使它异步的所有方式(这是我更喜欢的)。

来源:

Ensure that the await in the FooAsync method doesn’t find a context to marshal back to. The simplest way to do that is to invoke the asynchronous work from the ThreadPool, such as by wrapping the invocation in a Task.Run, e.g. int Sync() { return Task.Run(() => Library.FooAsync()).Result; } FooAsync will now be invoked on the ThreadPool, where there won’t be a SynchronizationContext, and the continuations used inside of FooAsync won’t be forced back to the thread that’s invoking Sync().

在我的情况下,'等待'从来没有完成,因为在执行请求时异常,例如服务器不响应等。用try. catch包围它来确定发生了什么,它也会优雅地完成你的“等待”。

public async Task<Stuff> GetStuff(string id)
{
    string path = $"/api/v2/stuff/{id}";
    try
    {
        HttpResponseMessage response = await client.GetAsync(path);
        if (response.StatusCode == HttpStatusCode.OK)
        {
            string json = await response.Content.ReadAsStringAsync();
            return JsonUtility.FromJson<Stuff>(json);
        }
        else
        {
            Debug.LogError($"Could not retrieve stuff {id}");
        }
    }
    catch (Exception exception)
    {
        Debug.LogError($"Exception when retrieving stuff {exception}");
    }
    return null;
}

我使用了很多等待,所以我没有得到响应,我转换为同步调用它开始工作

            using (var client = new HttpClient())
            using (var request = new HttpRequestMessage())
            {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                request.Method = HttpMethod.Get;
                request.RequestUri = new Uri(URL);
                var response = client.GetAsync(URL).Result;
                response.EnsureSuccessStatusCode();
                string responseBody = response.Content.ReadAsStringAsync().Result;