HttpClient.GetAsync(…) never returns when using await/async

前端 未结 6 773
[愿得一人]
[愿得一人] 2020-11-22 12:22

Edit: This question looks like it might be the same problem, but has no responses...

Edit: In test case 5 the task appears to be st

相关标签:
6条回答
  • 2020-11-22 12:58

    Edit: Generally try to avoid doing the below except as a last ditch effort to avoid deadlocks. Read the first comment from Stephen Cleary.

    Quick fix from here. Instead of writing:

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

    Try:

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

    Or if you need a result:

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

    From the source (edited to match the above example):

    AsyncOperation will now be invoked on the ThreadPool, where there won’t be a SynchronizationContext, and the continuations used inside of AsyncOperation won’t be forced back to the invoking thread.

    For me this looks like a useable option since I do not have the option of making it async all the way (which I would prefer).

    From the source:

    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().

    0 讨论(0)
  • 2020-11-22 13:05

    Since you are using .Result or .Wait or await this will end up causing a deadlock in your code.

    you can use ConfigureAwait(false) in async methods for preventing deadlock

    like this:

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

    you can use ConfigureAwait(false) wherever possible for Don't Block Async Code .

    0 讨论(0)
  • 2020-11-22 13:11

    These two schools are not really excluding.

    Here is the scenario where you simply have to use

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

    or something like

       AsyncContext.Run(AsyncOperation);
    

    I have a MVC action that is under database transaction attribute. The idea was (probably) to roll back everything done in the action if something goes wrong. This does not allow context switching, otherwise transaction rollback or commit is going to fail itself.

    The library I need is async as it is expected to run async.

    The only option. Run it as a normal sync call.

    I am just saying to each its own.

    0 讨论(0)
  • I'm going to put this in here more for completeness than direct relevance to the OP. I spent nearly a day debugging an HttpClient request, wondering why I was never getting back a response.

    Finally found that I had forgotten to await the async call further down the call stack.

    Feels about as good as missing a semicolon.

    0 讨论(0)
  • 2020-11-22 13:18

    In my case 'await' never finished because of exception while executing the request, e.g. server not responding, etc. Surround it with try..catch to identify what happened, it'll also completes your 'await' gracefully.

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 13:24

    You are misusing the API.

    Here's the situation: in ASP.NET, only one thread can handle a request at a time. You can do some parallel processing if necessary (borrowing additional threads from the thread pool), but only one thread would have the request context (the additional threads do not have the request context).

    This is managed by the ASP.NET SynchronizationContext.

    By default, when you await a Task, the method resumes on a captured SynchronizationContext (or a captured TaskScheduler, if there is no SynchronizationContext). Normally, this is just what you want: an asynchronous controller action will await something, and when it resumes, it resumes with the request context.

    So, here's why test5 fails:

    • Test5Controller.Get executes AsyncAwait_GetSomeDataAsync (within the ASP.NET request context).
    • AsyncAwait_GetSomeDataAsync executes HttpClient.GetAsync (within the ASP.NET request context).
    • The HTTP request is sent out, and HttpClient.GetAsync returns an uncompleted Task.
    • AsyncAwait_GetSomeDataAsync awaits the Task; since it is not complete, AsyncAwait_GetSomeDataAsync returns an uncompleted Task.
    • Test5Controller.Get blocks the current thread until that Task completes.
    • The HTTP response comes in, and the Task returned by HttpClient.GetAsync is completed.
    • AsyncAwait_GetSomeDataAsync attempts to resume within the ASP.NET request context. However, there is already a thread in that context: the thread blocked in Test5Controller.Get.
    • Deadlock.

    Here's why the other ones work:

    • (test1, test2, and test3): Continuations_GetSomeDataAsync schedules the continuation to the thread pool, outside the ASP.NET request context. This allows the Task returned by Continuations_GetSomeDataAsync to complete without having to re-enter the request context.
    • (test4 and test6): Since the Task is awaited, the ASP.NET request thread is not blocked. This allows AsyncAwait_GetSomeDataAsync to use the ASP.NET request context when it is ready to continue.

    And here's the best practices:

    1. In your "library" async methods, use ConfigureAwait(false) whenever possible. In your case, this would change AsyncAwait_GetSomeDataAsync to be var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
    2. Don't block on Tasks; it's async all the way down. In other words, use await instead of GetResult (Task.Result and Task.Wait should also be replaced with await).

    That way, you get both benefits: the continuation (the remainder of the AsyncAwait_GetSomeDataAsync method) is run on a basic thread pool thread that doesn't have to enter the ASP.NET request context; and the controller itself is async (which doesn't block a request thread).

    More information:

    • My async/await intro post, which includes a brief description of how Task awaiters use SynchronizationContext.
    • The Async/Await FAQ, which goes into more detail on the contexts. Also see Await, and UI, and deadlocks! Oh, my! which does apply here even though you're in ASP.NET rather than a UI, because the ASP.NET SynchronizationContext restricts the request context to just one thread at a time.
    • This MSDN forum post.
    • Stephen Toub demos this deadlock (using a UI), and so does Lucian Wischik.

    Update 2012-07-13: Incorporated this answer into a blog post.

    0 讨论(0)
提交回复
热议问题