Silverlight, имея дело с Асинхронными вызовами

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

        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
        }

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

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

6
задан Billy 22 February 2010 в 00:08
поделиться

8 ответов

(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.

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

0
ответ дан 17 December 2019 в 22:12
поделиться

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

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

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

0
ответ дан 17 December 2019 в 22:12
поделиться

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

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

Синхронные вызовы возможны в 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, она проверяет, запущена ли она в потоке UI, если да, то она перезапускает себя в фоновом потоке. Затем он устанавливает AutoResetEvent и выполняет асинхронный вызов. Затем он приостанавливает выполнение объекта myResetEvent до тех пор, пока он не будет установлен (или "сигнализирован") обработчиком завершения вызова, после чего выполнение кода продолжается. Обратите внимание, что вы не должны пытаться получить доступ к элементу управления непосредственно из такого кода, не убедившись предварительно, что вы вернулись в поток пользовательского интерфейса.

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

0
ответ дан 17 December 2019 в 22:12
поделиться

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

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

0
ответ дан 17 December 2019 в 22:12
поделиться
  1. Настройка статического события autoResetEvent
  2. Настройка элемента ThreadPoolQueueUserWorkItems
  3. ForEach в цикле, постановка элемента в очередь и передача autoResetEvent в качестве параметра обратного вызова.
  4. В обратном вызове выполните работу, а затем вызовите autoresetevent.Set ();
  5. В основном теле в точке // Я хочу выполнить код здесь после завершения каждого асинхронного вызова вызвать соответствующий autoResetEvent.Wait ();
0
ответ дан 17 December 2019 в 22:12
поделиться

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

0
ответ дан 17 December 2019 в 22:12
поделиться

Учитывая, что вы используете 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, заставляющего вас асинхронно загружать данные, заключается в том, что вам нужно много работать, чтобы заблокировать пользовательский интерфейс при вызове службы, что именно то, что вы собираетесь делать с таким кодом, если вы не пусть он работает в фоновом потоке, что я вам настоятельно рекомендую. Вы всегда можете отобразить сообщение «Подождите», пока не завершится фоновый процесс.

1
ответ дан 17 December 2019 в 22:12
поделиться

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

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

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

Поэтому я использовал такой класс управления:

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
ответ дан 17 December 2019 в 22:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: