Асинхронный запуск метода
Имеется класс Tcp клиента, моя идея проста: подключиться к серверу и запустить асинхронно метод прослушивания сообщений от сервера. При запуске метода ReadStream я специально не указываю await, чтобы не дожидаться его завершения, однако асинхронности все равно не происходит.
class Client : IDisposable
{
...
public async Task ConnectAsync(string host, int port)
{
await tcpClient.ConnectAsync(IPAddress.Parse(host), port);
if (tcpClient.Connected)
{
System.Console.WriteLine("Connected!");
var task = ReadStream(token);
activeTasks.Add(task);
System.Console.WriteLine("Task runned");
}
else
{
System.Console.WriteLine("Failed to connect :(");
}
}
...
private async Task<string> ReadStringFromStreamAsync(NetworkStream stream, int size)
{
var buffer = new byte[size];
int length = await stream.ReadAsync(buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer, 0, length);
}
private async Task ReadStream(CancellationToken token)
{
var stream = tcpClient.GetStream();
while (!token.IsCancellationRequested)
{
if (stream.DataAvailable)
{
string message = await ReadStringFromStreamAsync(stream, 1024);
System.Console.WriteLine("Recieved message: " + message);
}
}
}
}
Если запускать метод через Task.Run(()=>ReadStream(token)), то всё работает как мне нужно. Для меня это немного странно, потому что моей метод также возвращает Task и по моей логике (скорее всего неверной) при встречи неожидающего Task метод должен запускаться асинхронно. За всё время обучения уже раза три копался в этой теме и всё еще представление о функционировании данного механизма очень слабое. Поэтому будет здорово, если на примере решения данной проблемы до меня дойдет какая-то часть. Соответственно у меня два вопроса:
Почему метод
ReadStreamне запускается асинхронно несмотря на наличие в нёмawait(обычно без его указания пишется, что из-за его отсутствия метод выполняется синхронно)?Если всё же верно запускать данную операцию асинхронно через
Task.Run, то у меня возникает небольшая путаница в том, какой таск следует помещать в список активных задач:var task = Task.Run(()=>ReadStream(token)); activeTasks.Add(task);или
Task.Run(()=>{var task = ReadStream(token); activeTasks.Add(task);});
Ответы (1 шт):
Смотрите. Само по себе ключевое слово async вовсе не гарантирует, что метод будет асинхронным. Он лишь создаёт условия, при которых вы можете легко сами в вашем методе реализовать асинхронность.*
Одна из особенностей async-методов состоит в том, что метод выполняется в вызывающем потоке до первого await. В вашем случае у вас крутится синхронно цикл while (!token.IsCancellationRequested) до тех пор, пока stream.DataAvailable не станет равным true, то есть до прихода данных! До этого момента управление не возвращается назад в вызывающий метод.
Что нужно сделать? Нужно сделать ожидание данных асинхронным. Скорее всего, сработает просто вот такой код:
private async Task ReadStream(CancellationToken token)
{
var stream = tcpClient.GetStream();
while (!token.IsCancellationRequested)
{
string message = await ReadStringFromStreamAsync(stream, 1024);
System.Console.WriteLine("Recieved message: " + message);
}
}
(но гарантировать не могу, т. к. вы не привели код метода ReadStringFromStreamAsync).
Если ваш код просто читает строку из потока, то можно применить StreamReader:
private async Task ReadStream(CancellationToken token)
{
using var textReader = new StreamReader(tcpClient.GetStream());
while (!token.IsCancellationRequested)
{
string message = await textReader.ReadLineAsync();
System.Console.WriteLine("Recieved message: " + message);
}
}
*Как это сделать? Нужно, чтобы код вашего метода выполнялся быстро, не было холостых синхронных циклов ожидания наподобие while (!condition) { /* do nothing */ }, а все места, где вам нужно дождаться медленного результата (например, чтение данных из сети или с диска) были асинхронными. Если вам нужна пауза, она должна быть не через Thread.Sleep(), а через await Task.Delay(). И вы не должны вызывать синхронные медленные функции внутри, а пользоваться либо быстрыми, либо асинхронными.