Действительно ли асинхронные обратные вызовы Post + предотвращают очередь? - PullRequest
1 голос
/ 16 февраля 2010

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

if(control.InvokeRequried) { // invoke } 

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

Проблема, с которой я столкнулся, заключается в том, что мои сообщения по какой-то причине очень медленные, и, следовательно, они, похоже, немного забиты и продолжают работать после того, как я завершил свою работу. Пример краткого кода:

// called by user
WorkerEventHandler workerDelegate = new WorkerEventHandler(CalcAsync);
workerDelegate.BeginInvoke(p, asyncOp, null, null);

CalcAsync(P p, AsyncOperation asyncOp)
{
   Calculate();
   asyncOp.PostOperationCompleted(new AsyncCompletedEventArgs(null, false, 
      asyncOp.UserSuppliedState);
}

Calculate()
{
   DoStuff()            // updates the progress as it goes
   this.UpdateStatus("Done");   // marks us as done
   this.UpdateProgress(pId, 100);   // increase progress to max
}

При переходе я получаю повторные звонки на UpdateProgress , который вызывается из опубликованных событий, однако, похоже, что они не в состоянии следить и все еще публикуются. Они обрабатываются так:

// initalized with 
//    onProgressUpdatedDelegate = new SendOrPostCallback(UpdateProgress);    
private SendOrPostCallback onProgressUpdatedDelegate; 
public void UpdateProgress(Guid pId, ProgressEventArgs e)
{
   AsyncOperation asyncOp = GetAsyncOperation(pId);

   if (asyncOp != null)
   {
      asyncOp.Post(this.onProgressUpdatedDelegate, e);
   }
   else
   {
      UpdateProgress(this, e);
   }
}

private void UpdateProgress(object state)
{
   ProgressEventArgs e = state as ProgressEventArgs;
   UpdateProgress(this, e); // does the actual triggering of the EventHandler
}

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

Это говорит о том, что сообщение просто медленное? Я полагаю, что мне действительно хотелось бы, чтобы каждый раз, когда я звонил UpdateProgress через сообщение, я хочу удалить все существующие сообщения в очереди, если это так. Но я не уверен, что это возможно? Если это так, возможно ли очистить только эти события, так как я не хочу затем чистить мое событие UpdateStatus случайно ...

Редактировать

Некоторые из отсутствующих методов, если это облегчает.

public void UpdateProgress(Guid pId, ProgressEventArgs e)
{
   AsyncOperation asyncOp = GetAsyncOperation(pId);
   if (asyncOp != null)
   {
      asyncOp.Post(this.onProgressUpdatedDelegate, e);
   }
   else
   {
      UpdateProgress(this, e);
   }
}

private void UpdateProgress(object sender, ProgressEventArgs e)
{
   ProgressChangedEventHandler handler;
   lock (progressUpdateEventLock)
   {
      handler = progressUpdateEvent;
   }

   if (handler != null)
      handler(sender, e);
}

Ответы [ 2 ]

1 голос
/ 16 февраля 2010

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

Но, да, вызов Post () не очень быстрый. Цикл сообщений в потоке пользовательского интерфейса занимает примерно миллисекунду, чтобы получить уведомление, если он не занят чем-то другим. Это не одна миллисекунда тяжелой работы процессора, это задержка, вызванная подключением Windows, которое обрабатывает GetMessage (). Переключатель контекста потока является его частью, но только небольшой частью.

Это может иметь заметные неприятные побочные эффекты. Слишком частый вызов Post () или выполнение делегатом делегата слишком большой тяжелой работы может серьезно повлиять на поток пользовательского интерфейса. До такой степени, что он больше не справляется со своими обычными обязанностями. Из-за задержки вы можете добраться туда быстро; это займет всего 1000 сообщений в секунду.

Конечно, никогда не нужно публиковать так часто, человеческий глаз не может воспринимать обновления со скоростью, превышающей примерно 25 в секунду. Делать это чаще - просто напрасные усилия. Но получить эту идеальную частоту обновления не так-то просто, если рабочий поток не уделяет особое внимание прошедшему времени.

Во всяком случае, лучше оставить это на усмотрение клиентского кода, чтобы сообщить вам, как он хочет, чтобы его уведомления маршалировались. FileSystemWatcher.SynchronizingObject является хорошим примером этого. Если Post () развалится, это единственный способ для клиентского кода решить проблему. Также взгляните на класс BackgroundWorker.

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

В вашем примере отсутствует код? У вас есть вызов UpdateProgress(this, e), но нет соответствующего метода с этой подписью. Если this не является Guid, но я сомневаюсь в этом, так как тогда вы бы получили бесконечно рекурсивный метод. У вас также есть вызов UpdateProgress(100), и нет соответствующего метода.

В любом случае, возможно, было бы полезно, если бы вы использовали профилировщик или, если у вас его нет, Stopwatch, чтобы увидеть, где время тратится на ваши методы.

Возможно, что переключатели контекста потока убивают вас, потому что вы должны сделать InvokeRequired, а затем Invoke для каждого обновления. Если это так, вероятно, было бы полезно поместить Post в очередь и иметь таймер, который периодически очищает очередь. Что-то вроде:

Timer queueTimer = new Timer(QueueTimerProc, null, 20, 20);
queueTimer.Start();

void QueueTimerProc(object state)
{
    if (queue.Count > 0)
    {
        if (this.InvokeRequired)
           // Invoke the EmptyQueue method 
        else
           // Call the EmptyQueue method
    }
}

void EmptyQueue()
{
    lock (queue)
    {
        while (queue.Count > 0)
        {
            // dequeue an item and post the update
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...