ValueTask - правильное использование
прочитал статью про ValueTask - возникло пару вопросов
вот код из статьи:
int bytesRead;
{
ValueTask<int> readTask = _connection.ReadAsync(buffer);
if (readTask.IsCompletedSuccessfully)
{
bytesRead = readTask.Result;
}
else
{
using (_connection.RegisterCancellation())
{
bytesRead = await readTask;
}
}
}
оно вручную опрашивает, не закончился ли таск и если закончился, то делает все синхронно; не закончился - асинхронно.
и кое-что не понял:
- что это за странный метод
_connection.RegisterCancellation() - и разве компилятор не может сам вместо слова
awaitсделать разветвление на синхронный и асинхронный случай. может, в новых версиях? может, специально такого в компилятор не засовывают?
Ответы (1 шт):
Ваш вопрос можно разбить на 2 части
TaskvsValueTask- синхронное завершение метода vs асинхронное
Сразу скажу, технически эти 2 части вопроса между собой никак не связаны, но связаны логически.
Будет код синхронный или асинхронный решает не компилятор, а вы. Чтобы внедрить асинхронную машину состояний в метод, вы явно указываете ключевое слово async. А чтобы добавить состояния в эту машину, вы добавляете await.
Если на пути выполнения метода встретился await, то метод выполнится асинхронно в случае если await получит из вызванного метода незавершенный Task/ValueTask. В противном случае метод выполнится синхронно как обычный синхронный метод.
Самый простой пример такого ветвления - узнать свой IP:
class Program
{
private static readonly HttpClient client = new HttpClient();
private static string ipCache = null;
static async Task Main(string[] args)
{
await GetIP();
await GetIP();
await GetIP();
await GetIP();
Console.ReadKey();
}
private static async Task GetIP()
{
Task<string> task = GetMyIP();
Console.WriteLine(task.IsCompleted);
if (!task.IsCompleted)
await task;
}
private static async Task<string> GetMyIP()
{
if (ipCache == null)
{
string json = await client.GetStringAsync("https://api.myip.com/");
ipCache = JsonSerializer.Deserialize<Response>(json).ip;
}
return ipCache;
}
}
public class Response
{
public string ip { get; set; }
}
Если в этом коде все Task поменять на ValueTask (кроме Main) - ничего не изменится.
Вывод в консоль
False
True
True
True
Здесь первый вызов метода завершился асинхронно, а остальные синхронно.
А различие в том, что создать ValueTask дешевле и легче, чем Task, но он имеет свои ограничения. Например его нельзя ожидать одновременно в нескольких местах. В статье по вашей ссылке это должно быть написано. Я не читал ее, но читал оригинал, с которого она была переведена.
Итого, вам не стоит сильно запариваться о том, что там выдумал компилятор, но следует различать синхронное и асинхронное выполнение метода.
Использовать ли ValueTask? Не использовать, если ваш код не суперчувствителен к производительности. Например если вы пишете код клиентского приложения, можно вообще не думать об этом. А вот если вы пишете асинхронный код для сервера, который будет обслуживать много-много пользователей одновременно, то вот здесь вам ValueTask и пригодится как способ сэкономить на ресурсах.
Экономия эта будет ощутима только в случае высокой нагрузки. А чтобы ответить, откуда берется эта экономия, надо понять, чем значимые типы отличаются от ссылочных в плане производительности, надо понять, когда лучше использовать класс, а когда структуру, Task - класс, ValueTask - структура. Тогда и станет понятно, зачем это вся история с ValueTask была придумана.
Другими словами, безопаснее и проще использовать именно Task, используйте его по умолчанию. Если поймете, что создавать экземпляр класса на каждый вызов метода - это для вас медленно и дорого, внедряйте ValueTask, но это уже тонкости оптимизации.