шаблон для упаковки входящих параллельных запросов в один - PullRequest
0 голосов
/ 27 мая 2018

Предположим, у нас есть много случайно поступающих потоков, которые параллельно обращаются к одному и тому же ресурсу.Для доступа к ресурсу потока необходимо получить блокировку.Если бы мы могли упаковать N входящих потоков в один запрос, использование ресурсов было бы в N раз более эффективным.Также нам нужно ответить на индивидуальный запрос как можно быстрее.Каков наилучший способ / шаблон для этого в C #?

В настоящее время у меня есть что-то подобное:

//batches lock
var ilock = ModifyBatch.GetTableDeleteBatchLock(table_info.Name);
lock (ilock)
{
    // put the request into requests batch
    if (!ModifyBatch._delete_batch.ContainsKey(table_info.Name))
    {
        ModifyBatch._delete_batch[table_info.Name] = new DeleteData() { Callbacks = new List<Action<string>>(), ids = ids };
    }
    else
    {
        ModifyBatch._delete_batch[table_info.Name].ids.UnionWith(ids);
    }
    //this callback will get called once the job is done by a thread that will acquire resource lock
    ModifyBatch._delete_batch[table_info.Name].Callbacks.Add(f =>
    {
        done = true;
        error = f;
    });
}

bool lockAcquired = false;
int maxWaitMs = 60000;
DeleteData _delete_data = null;

//resource lock
var _write_lock = GetTableWriteLock(typeof(T).Name);
try
{
    DateTime start = DateTime.Now;
    while (!done)
    {
        lockAcquired = Monitor.TryEnter(_write_lock, 100);
        if (lockAcquired)
        {
            if (done) //some other thread did our job
                            {
                Monitor.Exit(_write_lock);
                lockAcquired = false;
                break;
            }
            else
            {
                break;
            }
        }
        Thread.Sleep(100);
        if ((DateTime.Now - start).TotalMilliseconds > maxWaitMs)
        {
            throw new Exception("Waited too long to acquire write lock?");
        }
    }
    if (done) //some other thread did our job
    {
        if (!string.IsNullOrEmpty(error))
        {
            throw new Exception(error);
        }
        else
        {
            return;
        }
    }

    //not done, but have write lock for the table
    lock (ilock)
    {
        _delete_data = ModifyBatch._delete_batch[table_info.Name];
        var oval = new DeleteData();
        ModifyBatch._delete_batch.TryRemove(table_info.Name, out oval);
    }
    if (_delete_data.ids.Any())
    {
        //doing the work with resource 
    }
    foreach (var cb in _delete_data.Callbacks)
    {
        cb(null);
    }
}
catch (Exception ex)
{
    if (_delete_data != null)
    {
        foreach (var cb in _delete_data.Callbacks)
        {
            cb(ex.Message);
        }
    }
    throw;
}
finally
{
    if (lockAcquired)
    {
        Monitor.Exit(_write_lock);
    }
}

1 Ответ

0 голосов
/ 27 мая 2018

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

Реализацияресурс блокировка (монитор) и List задач.

  1. Для каждого запроса:

  2. БлокировкаСписок, добавить текущее задание в список, помните номер.задач в списке, разблокируйте список.

  3. Попробуйте получить блокировку .

  4. Если неудачно:

    • Если номерзадач в списке <порог X, затем Return. </li>
    • В противном случае получить блокировку (заблокирует)
  5. заблокировать список, переместить его содержимое ввременный список, разблокируйте список.

  6. Если временный список не пуст

    • Выполните задачи в временном списке.

    • Повторите с шага 5.

  7. Снимите блокировку .

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

Настройтесь на оптимальный порог X (или измените его на пороговое значение на основе времени).


1 Если вам нужно подождать для задачи в объеме запроса, то вам нужно немного расширить процесс:

Добавить два поля в задачукласс: флаг завершения и исключение .

На шаге 4, перед возвратом, ждать до завершения задачи (Monitor.Wait) до флаг завершения становится true.Если исключение не равно null, выбросьте его.

На шаге 6 для каждой задачи установите флаг завершения и, необязательно, исключение а затем уведомить официантов (Monitor.PulseAll).

...