2013-11-30 2 views
1

Я делаю класс 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? Или это будет работать так, как я хочу, чтобы это было так?

ответ

3

tcs.SetCanceled(); // < - Да, это опечатка в .NET framework! :)

На самом деле, это странность в версии английского языка на английском языке. Британский (и другие) Englishes использовал бы Cancelled, но в США используется Canceled. Обратите внимание, что Cancellation всегда использует два l в каждой версии английского языка.

Так что я нужен CancellationToken, чтобы инициировать вызов Socket.close(), с помощью token.Register()

я не согласен. Соглашение об аннулировании заключается в том, что только то, что операция отменена.Поэтому, если у вас была операция , в которой был использован токен отмены, я с удивлением обнаружил бы, что семантика отмены отправки также устраняет любые операции Receive и далее делает весь сокет непригодным.

будет ли CancellationTokenRegistration удалено через использование до завершения асинхронного вызова?

Да. Здесь нет волшебного понимания части компилятора.

Нужно ли мне вообще заботиться об утилизации CancellationTokenRegistration здесь? Должен ли я хранить ссылку на него и вместо этого вызывать Dispose() в блоке finally? Или это будет работать так, как я хочу, чтобы это было так?

Я бы порекомендовал не использовать CancellationToken во всех индивидуальных вызовах. Это логически означает отмену на уровне объекта, поэтому создание отдельного класса и передача его в конструктор является более подходящим дизайном IMO. Но если вы настаиваете, блок finally звучит как хороший подход.

+0

Спасибо за быстрый ответ! При дальнейших исследованиях это действительно казалось бы, что это не опечатка. Ах, какой причудливый язык на английском! Что касается использования «CancellationToken», я вижу вашу точку зрения. Не имеет смысла использовать его против закрытия «Socket». Возможно, с помощью 'Connect' или' Accept' это имеет смысл, но не 'Send' или' Receive'. Реальный вопрос был в большей степени о сфере использования 'use' в этом случае, и если бы объект был удален досрочно, что, по-видимому, имеет место. Еще раз спасибо! – Erik

+0

Одним из решений было бы зарегистрировать аннулированное продолжение для 'Задачи ', которые вы возвращаете. Просто вставьте это перед оператором return: 'tcs.Task.ContinueWith (_ => socket.Close(), TaskContinuationOptions.OnlyOnCanceled)'. –

+0

@MikeStrobel: У вас все еще есть проблема, что отмена чтения закрывает весь сокет, а не только отменяет это чтение. –