С кодом, похожим на
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()
?
Вы думаете об этом неправильно. Вот псевдо версия того, что происходит внутри класса.
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();
}
Если вы не await
(или Wait
) задачи, которые они не отменяют себя. Они продолжают работать, пока не достигнут одного из трех статусов:
Однако в вашем случае, поскольку никто не ждет завершения задачи, используется область использования, которая предоставляет HttpClient
. Это, в свою очередь, отменяет все клиентские задачи, client.GetAsync(urlToInvoke)
в этом случае. Так что внутренняя задача async
немедленно закончится и станет отменена, а внешняя задача (Task.Run
) просто закончится, ничего не сделав.
Когда вы используете Task.Delay(5000).Wait()
который в основном является Thread.Sleep(5000)
задача имеет шанс завершить работу до завершения области использования. Однако этого режима работы следует избегать. Он блокирует поток во время Wait
и может привести к взаимоблокировкам в однопоточном SynchronizationContext
s. Это также скрывает возможные исключения в задаче (которая может снести приложение в более ранних версиях.Net)
Вы всегда должны ждать выполнения задач, желательно асинхронно, и, как комментирует Servy, нет причин использовать Task.Run
здесь для разгрузки, потому что GetAsync
является асинхронным и не будет блокировать вызывающий поток.
using (var client = new HttpClient())
{
var response = await client.GetAsync(urlToInvoke);
}
Task.Run
, поскольку сам метод является асинхронным и не будет блокировать текущий поток.
Task.Delay
?