Я делаю класс SocketExtender
, который предоставит async/await Task
методы расширения класса Socket
. В моих методах расширения я добавляю возможность отменить операцию Socket
, такую как ConnectAsync
, ReceiveAsync
или SendAsync
через параметр CancellationToken
.CancellationTokenRegistration.Dispose in Async Task
Прочитав оптимальный способ сделать это, я решил обернуть вызовы в TaskCompletionSource<T>
и передать обратно Task
для операции.
Например, я хочу, чтобы обернуть методы APM BeginReceive
и EndReceive
в следующем Task
-метод:
public static Task<int> ReceiveAsync(this Socket socket, byte[] buffer, int offset, int count, SocketFlags flags, CancellationToken token)
{
var tcs = new TaskCompletionSource<int>();
socket.BeginReceive(buffer, offset, count, flags, result =>
{
try
{
var bytesReceived = socket.EndReceive(result);
tcs.SetResult(bytesReceived);
}
catch(Exception ex)
{
if(token.IsCancellationRequested)
tcs.SetCanceled(); // <- Yes, this is a typo in the .NET framework! :)
else
tcs.SetException(ex);
}
});
return tcs.Task;
}
ПРИМЕЧАНИЯ: Приведенный выше метод не не на самом деле осуществить отмену! Да, он справится с этим, но это не приведет к тому, что Socket
фактически отменит операцию. Поэтому мне нужен CancellationToken
, чтобы вызвать вызов Socket.Close()
, через token.Register()
. Нет biggie, но вопрос становится как правильно ли я возвращаю объект CancellationTokenRegistration
?
Вот моя первая мысль:
public static Task<int> ReceiveAsync(this Socket socket, byte[] buffer, int offset, int count, SocketFlags flags, CancellationToken token)
{
var tcs = new TaskCompletionSource<int>();
using(token.Register(socket.Close)) // <- Here we are ensuring disposal of the CTR
{
socket.BeginReceive(buffer, offset, count, flags, result =>
{
try
{
var bytesReceived = socket.EndReceive(result);
tcs.SetResult(bytesReceived);
}
catch(Exception ex)
{
if(token.IsCancellationRequested)
tcs.SetCanceled(); // <- Yes, this is a typo in the .NET framework! :)
else
tcs.SetException(ex);
}
});
}
return tcs.Task;
}
Однако, мой вопрос заключается в том, что будет ли CancellationTokenRegistration
отводиться через using
до завершения асинхронного вызова? Мой первый инстинкт хочет сказать да, потому что вызов socket.BeginReceive
не будет блокироваться и немедленно возвращаться, в результате чего оператор using
очистится, как только метод вернет tcs.Task
.
Но опять же, возможно, компилятор C# «понимает» то, что я делаю, и увидит, что в дочерней области есть анонимный метод, и какая-то черная магия будет иметь место, чтобы она работала так, как я хочу.
Нужно ли мне вообще заботиться об утилизации CancellationTokenRegistration
? Должен ли я держать ссылку на него и звонить Dispose()
в блок finally
? Или это будет работать так, как я хочу, чтобы это было так?
Спасибо за быстрый ответ! При дальнейших исследованиях это действительно казалось бы, что это не опечатка. Ах, какой причудливый язык на английском! Что касается использования «CancellationToken», я вижу вашу точку зрения. Не имеет смысла использовать его против закрытия «Socket». Возможно, с помощью 'Connect' или' Accept' это имеет смысл, но не 'Send' или' Receive'. Реальный вопрос был в большей степени о сфере использования 'use' в этом случае, и если бы объект был удален досрочно, что, по-видимому, имеет место. Еще раз спасибо! – Erik
Одним из решений было бы зарегистрировать аннулированное продолжение для 'Задачи ', которые вы возвращаете. Просто вставьте это перед оператором return: 'tcs.Task.ContinueWith (_ => socket.Close(), TaskContinuationOptions.OnlyOnCanceled)'. –
@MikeStrobel: У вас все еще есть проблема, что отмена чтения закрывает весь сокет, а не только отменяет это чтение. –