不等待HttpClient使用时会发生什么

时间:2014-08-21 16:25:23

标签: c# .net asynchronous task-parallel-library dotnet-httpclient

给出类似于

的代码
Task.Run(() =>
{
    using (var client = new HttpClient())
    {
        var responseTask = client.GetAsync(urlToInvoke);
    }
});

在这种情况下,似乎GetAsync实际上并没有运作。任务在完成之前是否被取消或者实际发生了什么?

现在,如果你稍微改变一下并插入

Task.Run(() =>
{
    using (var client = new HttpClient())
    {
        var responseTask = client.GetAsync(urlToInvoke);

        Task.Delay(5000).Wait()
    }
});

GetAsync完全执行。这里发生了什么? Task.Delay是否responseTask将自己与responseTask.Wait()内的同一个任务相关联,最终使其等同于{{1}}?

2 个答案:

答案 0 :(得分:4)

当您完成await(或Wait)任务时,他们不会取消自己。他们继续跑,直到他们达到三种状态之一:

  • RanToCompletion - 成功完成。
  • 已取消 - 取消令牌已取消。
  • Faulted - 任务中发生未处理的异常。

但是,在您的情况下,因为没有人等待任务完成,所以使用范围结束处理HttpClient。这反过来将取消所有客户端的任务,在这种情况下client.GetAsync(urlToInvoke)。因此,内部async任务将立即结束并变为“已取消”,而外部任务(Task.Run)将在不执行任何操作的情况下结束。

当您使用基本为Task.Delay(5000).Wait()的{​​{1}}时,任务有可能在使用范围结束前完成。但是应该避免这种操作方式。它会阻塞整个Thread.Sleep(5000)中的线程,并可能导致单线程Wait中的死锁。这也隐藏了任务中可能存在的异常(可能会破坏早期版本的.Net中的应用程序)

你应该总是等待任务完成,最好是异步完成,而且正如Servy评论的那样,没有理由在这里使用SynchronizationContext进行卸载,因为Task.Run是异步的,并且赢得了#{1}}。 t阻止调用线程。

GetAsync

答案 1 :(得分:4)

你正在考虑错误。这是课堂内发生的事情的伪版本。

class HttpClient : IDisposeable
{
    private CancelationTokenSource _disposeCts;

    public HttpClient()
    {
        _disposeCts = new CancelationTokenSource();
    }

    public Task<HttpResponseMessage> GetAsync(string url)
    {
        return GetAsync(url, CancellationToken.None);
    }

    public async Task<HttpResponseMessage> GetAsync(string url, CancelationToken token)
    {
        var combinedCts =
            CancellationTokenSource.CreateLinkedTokenSource(token, _disposeCts.Token);
        var tokenToUse = combinedCts.Token;

        //... snipped code

        //Some spot where it would good to check if we have canceled yet.
        tokenToUse.ThrowIfCancellationRequested();

        //... More snipped code;

        return result;
    }

    public void Dispose()
    {
        _disposeCts.Cancel();
    }

    //... A whole bunch of other stuff.
}

重要的是要在退出using块时取消内部取消令牌。

在您的第一个示例中,任务尚未完成,因此tokenToUse现在会在调用ThrowIfCancellationRequested()时抛出。

在你的第二个例子中,任务已经完成,因此取消内部令牌的行为对由于它已经达到完成状态而返回的任务没有影响。

这就像问为什么这会导致任务被取消。

using (var client = new HttpClient())
{
    var cts = new CancellationTokenSource()
    var responseTask = client.GetAsync(urlToInvoke, cts.Token);

    cts.Cancel();
}

但这不是

using (var client = new HttpClient())
{
    var cts = new CancellationTokenSource()
    var responseTask = client.GetAsync(urlToInvoke, cts.Token);

    Task.Delay(5000).Wait()
    cts.Cancel();
}