.NET отмена асинхронных операций ввода-вывода - PullRequest
4 голосов
/ 11 сентября 2011

В этом конкретном случае я пишу сервер TCP / IP и использую TcpListener.BeginAcceptTcpClient () для приема входящих соединений. Логика сервера реализована в одном классе, реализующем интерфейс IDisposable: когда вызывается Dispose (), я хочу отменить операцию BeginAcceptTcpClient ().

Теперь в более общих чертах: как использовать отмену операций при использовании модели асинхронного ввода-вывода .NET (Begin * и End *)?

Моя идея была бы примерно такой:

private volatile bool canceledFlag = false;

this.listener.BeginAcceptTcpClient(asyncResult =>
{
    if(this.canceledFlag) return;

    // ...
}, null);

Dispose () установит для флага значение true и также вызовет this.listener.Stop (). Однако я вспоминаю, что прочитал, что для каждого вызова Begin * должен быть соответствующий вызов End *, иначе могут произойти плохие события.

Так как мне тогда это сделать? Обратите внимание, что я ищу общее решение для отмены с помощью методов Begin * и End * - я просто дал вам конкретный сценарий использования, чтобы помочь вам понять мой вопрос.

Ответы [ 3 ]

7 голосов
/ 11 сентября 2011

Решение general заключается в вызове Close () или Dispose () для любого объекта, чей метод BeginXxxx () вы вызвали. Это вызовет обратный вызов, когда вы вызовете EndXxxx (), тогда вы получите исключение ObjectDisposedException. Который вы должны поймать и воспринимать как сигнал об окончании использования, очистить и сразу же выйти из обратного вызова. Несмотря на неодобрение, использование исключения для управления потоком неизбежно.

Для TcpListener используйте Server.Dispose (), немного более эффективный, чем вызов Stop ().

1 голос
/ 11 сентября 2011

AsyncCallback вызывается после завершения асинхронного процесса. Это позволяет асинхронному потоку воссоединиться с оригиналом, и тогда могут возникать любые исключения (а не теряться в фоновом режиме). Это также позволяет вам захватывать любые возвращаемые значения.

Как правило, вы бы использовали шаблон Begin / End следующим образом:

  Action action = () = > DoSomething();
  action.BeginInvoke(action.EndInvoke, null);

или

 Action action = () = > DoSomething();
 action.BeginInvoke(OnComplete, null);

 private void OnComplete(IAsyncResult ar)
 {
     AsyncResult result = (AsyncResult)ar;
     var caller = (Action)result.AsyncDelegate;
     caller.EndInvoke();

     // do something else when completed
 }

Это зависит от конкретных реализаций, чтобы обеспечить способ отменить асинхронный процесс до его завершения. Обычно это делается с помощью реализации CancelAsync, которая внутренне останавливает задачу раньше. В случае TcpListener вы можете просто позвонить listener.Server.Dispose();

 TcpListener listener = new TcpListener(port);
 IAsyncResult = listener.BeginAcceptTcpClient(listener.EndAcceptTcpClient, null);

 // on dispose
 listener.Server.Dispose();
1 голос
/ 11 сентября 2011

Альтернативой, которую я бы порекомендовал, является не использование шаблона async-Begin-End, а задачи.

Вы можете использовать одну из перегрузок TaskFactory.FromAsync , чтобы создать задачу из ваших методов Begin / End и использовать ее. Вы можете использовать эту перегрузку .ContinueWith , чтобы указать продолжение с вашим собственным CancellationToken, чтобы остановить его. ИМХО, рекомендуется использовать Задачи всякий раз, когда вы можете, потому что следующая версия C # будет опираться на Задачу с поддержкой асинхронного ожидания / ожидания.

Вот хорошая статья , объясняющая общие закономерности и задачи

Замечание:

В своем первоначальном ответе я сказал, что вы можете использовать .Dispose, чтобы убить задачу, но это может вызвать серьезные проблемы, потому что вы можете утилизировать задачи только в состоянии Completed.

...