Я использую System.Net.WebSockets.ClientWebSocket
для создания клиента C # WebSocket для подключения к серверу, которому не принадлежит исходный код. Пока все работает хорошо, но я хочу «правильно» отключить моего клиента. Вот сокращенная версия моего исходного кода:
public class Client : IDisposable
{
private ClientWebSocket socket;
private string endpoint;
private Task receiveTask;
public Client(string endpoint)
{
this.endpoint = endpoint;
this.socket = new ClientWebSocket();
}
public async Task Initialize()
{
byte[] contentBuffer = Encoding.UTF8.GetBytes("notify message");
// Connect to the server, and send a message to notify
// it of the client's availability to receive data.
await OpenConnection();
await socket.SendAsync(new ArraySegment<byte>(contentBuffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
private async Task OpenConnection()
{
if (socket.State != WebSocketState.Open)
{
await socket.ConnectAsync(new Uri(endpoint), CancellationToken.None);
receiveTask = Task.Run(async () => await Receive());
}
}
private async Task Receive()
{
while (socket.State == WebSocketState.Open)
{
byte[] buffer = new byte[1024];
var result = await m_sessionSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
break;
}
else
{
using (var stream = new MemoryStream())
{
stream.Write(buffer, 0, result.Count);
while (!result.EndOfMessage)
{
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
stream.Write(buffer, 0, result.Count);
}
stream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
string message = reader.ReadToEnd();
// Do stuff with received message
}
}
}
}
}
public void Dispose()
{
// NOTE: This is a gross oversimplification. Assume in the
// actual project that a proper implementation of the Dispose
// pattern has been created.
if (socket.State == WebSocketState.Open)
{
// How do I notify the server of disconnection?
// The below has some sort of race condition, whereby
// it hangs the client.
Task.Run(async () => await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None)).Wait();
}
if (receiveTask != null)
{
receiveTask.Dispose();
}
socket.Dispose()
}
}
Теперь за вызов, который у меня есть. Я не могу избавиться от Task
, выполняющего получение данных, пока оно не закончится. Эта задача заблокирована в ожидании получения от сервера. Попытка дозвониться до CloseAsync
изнутри Dispose
, похоже, зашла в тупик, поэтому это может быть неправильный подход.
Очевидно, я мог бы использовать CancellationTokenSource
, чтобы передать CancellationToken
методу Receive
, передав его, в свою очередь, методу ReceiveAsync
WebSocket. Если я сделаю это и скажу источнику токена пометить токен, он освободит блок, и я смогу проверить токен на наличие флага IsCancellationRequested
внутри метода receive, который завершится, когда его увидят. Однако, если я это сделаю, это вызовет исключение. Мне действительно нужно поймать исключение (yuck!) В моем методе Dispose только для того, чтобы отменить задачу? Кроме того, если я отменяю задачу, это правильно уведомляет сервер об отключении? Или я просто делаю это неправильно, в первую очередь из-за того, что долгосрочный (не ожидаемый) Task
метод ReceiveAsync
работает?