Silverlight, имеющий дело с асинхронными вызовами - PullRequest
6 голосов
/ 22 февраля 2010

У меня есть код, указанный ниже:

        foreach (var position in mAllPositions)
        {
                 DoAsyncCall(position);
        }
//I want to execute code here after each Async call has finished

Так, как я могу сделать выше?
Я мог бы сделать что-то вроде:

        while (count < mAllPositions.Count)
        { 
            //Run my code here
        }

и число приращений после каждого асинхронного вызова ... но это не похоже на хороший способ сделать это

Любой совет? Есть ли какой-то шаблон проектирования для вышеуказанной проблемы, поскольку я уверен, что это распространенный сценарий?

Ответы [ 8 ]

1 голос
/ 12 июля 2010

Учитывая, что вы используете Silverlight, и у вас нет доступа к семафорам, вы можете искать что-то вроде этого (написано в блокноте, поэтому нет никаких обещаний относительно идеального синтаксиса):

int completedCallCount = 0;
int targetCount = mAllPositions.Count;

using (ManualResetEvent manualResetEvent = new ManualResetEvent(false))
{
    proxy.DoAsyncCallCompleted += (s, e) =>
    {
        if (Interlocked.Increment(ref completedCallCount) == targetCount)
        {
            manualResetEvent.Set();
        }
    };

    foreach (var position in mAllPositions)
    {
        proxy.DoAsyncCall(position);
    }

    // This will wait until all the events have completed.
    manualResetEvent.WaitOne();
}

Однако , одно из преимуществ Silverlight, заставляющего вас асинхронно загружать данные, заключается в том, что вам приходится усердно работать, чтобы заблокировать пользовательский интерфейс при совершении вызовов службы, и это именно то, что вы собираетесь делать. делать с таким кодом, если только он не запущен в фоновом потоке, что я настоятельно рекомендую вам сделать. Вы всегда можете отобразить сообщение «Пожалуйста, подождите», пока не завершится фоновый процесс.

0 голосов
/ 09 апреля 2010

Другой вариант - увеличить счетчик в цикле foreach, который вызывает асинхронные процессы. Затем при проверке обратного вызова вы уменьшите счетчик и проверите равенство нулю. Когда счетчик обнуляется, все вызовы завершены, и ваш следующий код может быть запущен. Просто убедитесь, что вы измените счетчик в потоке безопасным способом. Самый простой способ сделать это - вернуться к потоку пользовательского интерфейса перед уменьшением и проверкой счетчика.

0 голосов
/ 10 июля 2010

Я не рассматриваю это как проблему "Я хочу сделать синхронный вызов". Все, что вы действительно делаете, это связываете серию асинхронных вызовов, когда они все завершены.

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

В этой ситуации вы не хотите блокировать поток пользовательского интерфейса, пока обновляются все элементы (блокировка потоков - это зло, если есть альтернатива).

Итак, я использовал класс управления, подобный этому:

public class GroupAction
{
    public delegate void ActionCompletion();

    private uint _count = 0;
    private ActionCompletion _callback;
    private object _lockObject = new object();

    public GroupAction(uint count, ActionCompletion callback)
    {
        _count = count;
        _callback = callback;
    }

    public void SingleActionComplete()
    {
        lock(_lockObject)
        {
            if (_count > 0)
            {
                _count--;
                if (_count == 0) _callback();
            }
        }
    }
}

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

Код вызова будет выглядеть примерно так (я основал пример на вызове стандартной веб-службы временного преобразования, которую можно найти на w3schools.com).

    private void DoGroupCall()
    {
        uint count = 5;
        GroupAction action = new GroupAction(count,
            () =>
            {
                MessageBox.Show("Finished All Tasks");
            });

        for (uint i = 0; i < count; i++)
        {
            TempConvertHttpPost proxy = new TempConvertHttpPostClient(new BasicHttpBinding(), new EndpointAddress("http://localhost/webservices/tempconvert.asmx"));
            CelsiusToFahrenheitRequest request = new CelsiusToFahrenheitRequest() { Celsius = "100" };

            proxy.BeginCelsiusToFahrenheit(request,
                  (ar) => Deployment.Current.Dispatcher.BeginInvoke(
                      () =>
                      {
                          CelsiusToFahrenheitResponse response = proxy.EndCelsiusToFahrenheit(ar);

                          // Other code presumably...

                          action.SingleActionComplete();
                      }
                  ), null);
        }
    }

Обратите внимание, как экземпляр GroupAction определяется с помощью встроенного делегата обратного вызова, а затем функция SingleCallBack вызывается каждый раз, когда служба возвращается.

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

0 голосов
/ 13 марта 2010
  1. Настройка статического autoResetEvent
  2. Настройка ThreadPoolQueueUserWorkItems
  3. Элемент ForEach в цикле, поставьте его в очередь и передайте autoResetEvent в качестве параметра обратного вызова.
  4. В обратном вызове выполните работу, а затем вызовите autoresetevent.Set ();
  5. В основном теле в точке //I want to execute code here after each Async call has finished вызовите соответствующий autoResetEvent.Wait ();
0 голосов
/ 26 февраля 2010

Вы можете делать великие дела, следуя концепции рабочих процессов Async (перейдите к абзацу C #), как это показано в блоге Томаса.

http://tomasp.net/blog/csharp-async.aspx

0 голосов
/ 22 февраля 2010

(NB, я даю два отдельных ответа; это совершенно другой подход, чем в вопросе.)

Следуя примеру кода Мигеля Мадеро по адресу по этому URL , используйте Reactive Framework, чтобы параллельно ждать завершения всех результатов, а затем выполните другое задание.

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

0 голосов
/ 22 февраля 2010

Ваш вопрос немного неясен (это должно быть count <=?), Но здесь идет речь ...

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

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

private AutoResetEvent myResetEvent;

private void MyCallFunction(object someParameter) {

    if (this.Dispatcher.CheckAccess())
    {
        Action<object> a = new Action<object>(MyCallFunction);
        a.BeginInvoke(someParameter, null, null);
        return;
    }

    myResetEvent = new AutoresetEvent();
    myAsyncCall.CallCompleted += new EventHandler<>(myAsyncCall_CallCompleted);
    myAsyncCall.DoAsyncCall(someParameter);

    myResetEvent.WaitOne();
    //increment your count here
}

private void myAsyncCall_CallCompleted(object sender, SomeEventArgs e) {
    if (e.Error == null && !e.Cancelled) {
        if (myResetEvent != null)
            myResetEvent.Set();
    }
}

Обратите внимание, что этот код не особо ориентирован на многопоточность или готов к работе - это всего лишь быстрый пример.

Что происходитздесь, когда вы вводите MyCallFunction, он проверяет, запущен ли он в потоке пользовательского интерфейса, если это так, то он повторно вызывает себя в фоновом потоке.Затем он устанавливает AutoResetEvent и выполняет асинхронный вызов.Затем он приостанавливает работу объекта myResetEvent до тех пор, пока он не будет установлен (или «сигнализирован») из обработчика завершения вызова, после чего выполнение кода точки продолжается.Обратите внимание, что вы не должны пытаться получить доступ к элементу управления непосредственно из кода, подобного этому, без предварительного подтверждения того, что вы снова в потоке пользовательского интерфейса.

Когда я начал работать над тем, как это сделать в Silverlight, я начал с это ТАК сообщение , и продолжено с этой ссылкой .Но, как говорит Марк Гравелл в этом первом посте, не делайте этого, если сможете избежать этого.Единственный раз, когда мне нужно было это сделать, - это когда мне нужно было объединить результаты нескольких разрозненных вызовов WCF в один результат, который был возвращен в пользовательский интерфейс (и эти вызовы не могли быть объединены в один метод WCF в стиле фасада).

0 голосов
/ 22 февраля 2010

(NB, я даю вам два отдельных ответа. Один из них максимально приближен к вашему вопросу.)

Ваш вопрос говорит

while{ count >= mAllPositions.Count )
{
    //Run my code here
}

но я предполагаю, что вы действительно имеете в виду:

while( count < mAllPositions.Count )
   ; // do nothing -- busy wait until the count has been incremented enough
// Now run my code here

??

Если это так, то вы можете сделать то же самое более эффективно, используя семафор:

Semaphore TheSemaphore = new Semaphore( 0, mAllPositions.Count );

После завершения каждого асинхронного вызова освободите семафор.

TheSemaphore.Release();

Перед выполнением окончательного кода убедитесь, что семафор был выпущен необходимое количество раз:

 for( int i=0; i<mAllPositions.Count; i++ )
    TheSemaphore.WaitOne();
 // Now run follow-on code.

Ваш код будет блокироваться, пока все асинхронные операции не будут завершены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...