отвечать на асинхронные вызовы sql по мере их завершения - PullRequest
2 голосов
/ 11 февраля 2012

Справочная информация:

У меня есть служба, которая запрашивает в БД исторические результаты в пакетном режиме. Пакеты основаны на времени начала и времени окончания. Данные между всеми пакетами являются взаимоисключающими, поэтому для данного набора пакетов они могут запускаться в любом порядке. Некоторые партии могут занять больше времени, чем другие. Если какой-либо пакет завершается неудачно, весь процесс прерывается / останавливается, и ошибка регистрируется. При возврате данных клиенту необходимо объединить данные из всех пакетов.

Проблема:

Верните данные клиенту как можно быстрее.

Исходные решения:

Первоначально я выполнял партии по порядку синхронно. При этом не использовался тот факт, что данные являются взаимоисключающими. После первоначального использования было обнаружено, что этот метод загрузки занимает слишком много времени. После некоторой отладки я обнаружил, что основной причиной медлительности было время выполнения SQL-запросов. Поэтому следующим решением было попытаться выполнить каждый пакет асинхронно, используя BeginExecuteReader() и EndExecuteReader(). После асинхронного вызова всех пакетов служба будет ждать в цикле while и опрашивать каждые 300 мс, чтобы проверить, завершен ли какой-либо из запросов. Если бы это было так, то это было бы прочитано.

int batchCount = 0, resultCount = 0;
List<AsyncDataResult> asyncCalls = new List<AsyncDataResult>();

while (true)
{
    if (asyncCalls.Count == 0)
    {
        break;
    }

    if ((asyncCalls[resultCount]).AsyncResult.IsCompleted)
    {
        ReadAsyncResultsFromDb(asyncCalls[resultCount]);
        asyncCalls.RemoveAt(resultCount);
    }

    resultCount++;
    if (resultCount >= asyncCalls.Count)
    {
        resultCount = 0;
        Thread.Sleep(300);
    }
}

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

Вопрос:

  1. Как выполнить sql асинхронно, но не выполнять опрос?
  2. Начать чтение каждой партии, как только она завершится?

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

Ответы [ 3 ]

2 голосов
/ 11 февраля 2012

Зачем активно опрашивать?Каждая асинхронная операция, которую вы вызываете, возвращает IAsyncResult с WaitHandle.Используйте WaitAny() и позвольте системе уведомить вас:

/// <summary>
/// Do something useful with a completed query
/// </summary>
/// <param name="result"></param>
/// <returns>
///   true if the query failed;
///   false if the query was successful
/// </returns>
private static bool DoSomethingUseful( IAsyncResult result )
{
    throw new NotImplementedException() ;
}

static void Main( string[] args )
{
    List<IAsyncResult> batch         = SpawnBatch() ;
    bool               errorOccurred = ProcessCompletedBatches( batch , DoSomethingUseful) ;

    if ( errorOccurred )
    {
        CancelPending( batch ) ;
    }

    return ;

}

public static bool ProcessCompletedBatches( List<IAsyncResult> pending , Func<IAsyncResult,bool> resultHandler )
{
    bool errorOccurred = false ;

    while ( ! errorOccurred && pending.Count > 0 )
    {
        WaitHandle[] inFlight = pending.Select( x => x.AsyncWaitHandle ).ToArray() ;

        int offset = WaitHandle.WaitAny( inFlight ) ;

        IAsyncResult result = pending[offset] ;
        pending.RemoveAt(offset) ;

        errorOccurred = resultHandler(result) ;

    }

    return errorOccurred ;

}
1 голос
/ 11 февраля 2012

Недостаточно информации, чтобы предложить путь, по которому вы должны идти, но, определенно, вы могли бы, и это будет библиотека «Параллельная задача» и Task<T>.

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

Если, скажем, T, был подключен к БД, бросить в него запрос, вернуть заголовок данных и сказать, что в пакете было 8 запросов.

Вы устанавливаете задачи с 0 по 7, запускаете таймер для тайм-аута, отключаете их все. Когда они завершатся, вы сохраните читатель и установите свой флаг на основе идентификатора задачи. Когда он достигнет 255, вызовет событие OnBatchComplete, скопируйте ваши читатели и передайте их в задачу объединения. Тайм-аут уходит первым, действуйте соответственно. Если в задаче есть ошибка, пусть она вернет какую-то подходящую причину, сообщит вызывающей стороне, возможно, уничтожит все еще выполняющиеся запросы.

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

Это нечестно, я не получаю такие забавные вещи, как это ...

0 голосов
/ 11 февраля 2012

Вместо опроса необходимо использовать асинхронный обратный вызов. http://msdn.microsoft.com/en-us/library/system.asynccallback.aspx

Что такое AsyncCallback?

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