Синхронизация асинхронного вызова в С#

Я столкнулся с довольно неловким затруднительным положением в проекте на работе. Нам нужно создать пользователей для 4 или 5 разных сервисов и настроить их таким образом, чтобы в случае сбоя одного из них не работали все. Они инкапсулированы в блок области действия транзакции.

Одна из служб, в которую нам нужно добавить пользователей, требует подключения по телнету и подделки некоторых данных. Есть и другие способы сделать это (которые стоят денег), но пока это то, с чем мы застряли. Добавление одного пользователя занимает примерно 3 минуты. Мы собираемся работать над тем, чтобы значительно снизить это, насколько можно себе представить, но это не совсем главное. Этот вызов является асинхронным и должен работать правильно. Суть в том, что к сервису может быть не более 10 подключений.

Наш проект настроен на создание пользователей в пакетном режиме. Таким образом, потенциально 50 пользователей создаются одновременно. Это создает проблему, когда через telnet можно выполнить только 10 подключений, а обработанный пользователь вряд ли займет много времени, кроме службы telnet. Теперь мне нужно синхронизировать этот процесс, чтобы остальные не могли продолжаться, пока он не завершится.

Мы используем обратные вызовы и делегаты с асинхронными вызовами для реализации функциональности. Каков наилучший способ инкапсулировать асинхронную часть и не продолжать, пока она не будет завершена?

Должны ли мы настроить цикл, который завершается только после завершения вызова? Есть ли что-то в библиотеке Threading, что могло бы помочь? Я никогда раньше не работал с потоками, так что это будет для меня впервые. Какие есть инструменты для решения этой проблемы?

РЕДАКТИРОВАТЬ:

Если я использую шаблон BeginInvoke/EndInvoke, будут ли асинхронные вызовы в первом делегате также учитывать начало/конец?

Пример:

public void dele1(string message) {
    Console.Write(message);
    delegate2 del2 = new delegate2;
    del2();
    Console.Write("End of Delegate 2");
}

public void dele2() {
    // Long Processing
    Console.Write("Delegate 2");
}

public delegate void delegate1(String message);
public delegate void delegate2();

delegate1 del1 = new delegate1(dele1);
del1("Delegate 1").BeginInvoke;
del1().EndInvoke;
Console.Write("End of Delegate 1");

// Ожидаемый результат (End Invoke ожидает завершения делегата 2):

Delegate 1
End of Delegate 2
Delegate 2
End of Delegate 1

// Или (End Invoke ожидает только завершения делегата 1, но не каких-либо внутренних вызовов делегата):

Delegate 1
End of Delegate 2
End of Delegate 1
Delegate 2

Будет ли завершен вызов вызова, пока второй делегат также не завершит обработку? Или мне нужно будет использовать шаблоны вызова для всех вызовов делегатов?


person Josh Smeaton    schedule 16.03.2009    source источник
comment
Не уверен в вашем синтаксисе для отредактированного примера. Можете ли вы уточнить, что происходит с del1?   -  person strager    schedule 17.03.2009


Ответы (6)


Вы действительно можете использовать мониторы, семафоры или даже ждать, пока ваш асинхронный вызов метода не будет выполнен.

Но вы также можете получить это бесплатно. Если вы вызываете EndInvoke() для делегата, который ранее был запущен с помощью BeginInvoke(), вы блокируетесь до тех пор, пока не будет выполнена асинхронная работа.

Не уверен, поможет ли это, потому что вы должны использовать этот асинхронный шаблон. Если это так, вы получаете асинхронное выполнение (и преобразование асинхронных обратно в синхронные вызовы) бесплатно.

Ознакомьтесь с разделом Асинхронный вызов синхронных методов в MSDN для получения дополнительной информации об этом шаблоне. .

Надеюсь, это поможет!

person Aaron    schedule 16.03.2009
comment
Похоже, это именно то, что мне нужно! Спасибо друг. - person Josh Smeaton; 16.03.2009

Мне кажется, вам нужна очередь... если есть запрос, добавьте в очередь (с Monitor); когда вызов завершится, проверьте очередь...

person Marc Gravell    schedule 16.03.2009

IAsyncResult имеет свойство AsyncWaitHandle, которое дает вам дескриптор ожидания (если он доступен), который будет сообщен, когда операция завершится.

Таким образом, вы можете использовать WaitHandle.WaitAll (или .WaitAny) для выполнения невращающихся ожиданий на многих дескрипторах одновременно.

person Richard    schedule 16.03.2009

У вас есть несколько вариантов. Вот пара идей -

Во-первых, вы можете использовать ManualResetEvent, чтобы " block" основной поток, пока все ваши асинхронные операции не будут завершены. Идея состоит в том, чтобы основной поток вызывал вашу функцию, а затем выполнял event.WaitOne(), а функция отвечала за настройку события.

В качестве альтернативы вы можете попробовать использовать что-то вроде Семафор. Он разработан, чтобы помочь в таких ситуациях, поскольку вы можете ограничить его до 10 одновременных событий, не беспокоясь о том, чтобы пытаться справиться с этим самостоятельно. Это, вероятно, был бы мой предпочтительный подход.

person Reed Copsey    schedule 16.03.2009

Есть две причины для использования потоков:

  • чтобы использовать ресурсы процессора, чтобы ускорить процесс
  • написать несколько одновременных конечных автоматов как простые методы, что делает их более читабельными

Недостатком использования нитей является:

  • это действительно сложно

Если узким местом является 3-минутное ожидание на удаленной машине, возможно, стоит отметить, что несколько потоков не дадут вам никакого преимущества в производительности. Вы можете написать все это в однопоточном режиме, поддерживая набор из десяти объектов «машины состояний» и перемещая их по этапам создания пользователя, все из одного потока, с небольшой разницей в общей производительности.

Итак, что вы, возможно, ищете, это приятный для чтения макет кода, превращающий операцию создания пользователя в последовательность вызовов, которые читаются как один метод (какими они были бы, если бы вы запускали их как поток).

Другой способ получить это — использовать метод итератора, т. е. содержащий операторы yield return, что также дает вам преимущества читаемости одного метода. У меня должна быть три ссылки за последнюю неделю! Это статья Джеффри Рихтера об AsyncEnumerator.

person Daniel Earwicker    schedule 16.03.2009

Я немного не понимаю, как вы используете TransactionScope (поэтому я могу ошибаться), но вы можете создавать DependentTransactions, которые можно распределять между рабочими потоками. Ваша транзакция верхнего уровня не будет зафиксирована до тех пор, пока все DependentTransactions не будут успешно зафиксированы, и наоборот с откатом. Я нашел это как простой способ сделать все или ничего между потоками, если операция, которую вы вызываете, инкапсулирует функциональность фиксации/отката (IEnlistmentNotification или тому подобное).

person jsw    schedule 16.03.2009
comment
Вы правы, и если бы мы не ограничивались 10 максимальными подключениями для финального процесса, все было бы в порядке. К сожалению, мы ограничены 10 (на самом деле нам разрешено 2), поэтому проще делать их все подряд, чем стоять в очереди. - person Josh Smeaton; 17.03.2009
comment
Ограниченное количество одновременных подключений... всегда интересное требование;) - person jsw; 17.03.2009