Делать больше удаленных вызовов, чем потоков, делая синхронные методы асинхронными - PullRequest
0 голосов
/ 01 июля 2018

У меня есть куча удаленных вызовов, которые все синхронны (сторонняя библиотека). Большинство из них занимают много времени, поэтому я не могу использовать их чаще, чем 5-10 раз в секунду. Это слишком медленно, потому что мне нужно звонить им, по крайней мере, 3000 раз каждые пару минут и даже больше, если службы были остановлены на некоторое время. На клиенте практически нет работы с процессором. Он получает данные, проверяет некоторые простые условия и делает еще один вызов, которого он должен ждать.

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

Я думал о том, чтобы вызывать их с Task.Run, но в каждой прочитанной статье говорится, что она предназначена для работы с процессором и использует потоки из пула потоков. Если я получу это правильно, при таком подходе я не смогу нарушить ограничение потока, не так ли? Так какой подход лучше всего подходит здесь?

А как насчет Task.FromResult? Могу ли я ожидать таких методов асинхронно в большем количестве, чем существует потоков?

public async Task<Data> GetDataTakingLotsOfTime(object id)
{
    var data = remoting.GetData(id);
    return await Task.FromResult(data);
}

Ответы [ 2 ]

0 голосов
/ 02 июля 2018

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

Временами, когда ваш поток бездействует, ожидая завершения других процессов, таких как запись на диск, запрос базы данных или выборка информации из Интернета, обычно это ситуации, когда вы видите асинхронную функцию рядом с неасинхронной функцией: Write и WriteAsync, Send и SendAsync.

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

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

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

У пользователей семинара есть одна точка доступа (фронт-офис?), Где они отправляют запрос на выполнение чего-либо и ждут результата.

Для этого я использовал System.Threading.Tasks.Dataflow.BufferBlock . Установите пакет Nuget TPL Dataflow.

Вы можете посвятить свою мастерскую приему только работы на GetDataTakingLotsOfTime; Я сделал свой семинар универсальным: я принимаю каждую работу, которая реализует интерфейс IWork:

interface IWork
{
    void DoWork();
}

В WorkShop есть два BufferBlocks: один для ввода рабочих запросов и один для вывода готовой работы. В мастерской есть поток (или несколько потоков), который ожидает на входе BufferBlock, пока не придет работа. Выполняет ли Work, а после завершения отправляет задание на вывод BufferBlock

class WorkShop
{
    public WorkShop()
    {
         this.workRequests = new BufferBlock<IWork>();
         this.finishedWork = new BufferBlock<IWork>();
         this.frontOffice = new FrontOffice(this.workRequests, this.finishedWork);
    }

    private readonly BufferBlock<IWork> workRequests;
    private readonly BufferBlock<IWork> finishedWork;
    private readonly FrontOffice frontOffice;

    public FrontOffice {get{return this.frontOffice;} }

    public async Task StartWorkingAsync(CancellationToken token)
    {
        while (await this.workRequests.OutputAvailableAsync(token)
        {   // some work request at the input buffer
            IWork requestedWork = this.workRequests.ReceiveAsync(token);
            requestedWork.DoWork();
            this.FinishedWork.Post(requestedWork);
        }
        // if here: no work expected anymore:
        this.FinishedWork.Complete();
    }

    // function to close the WorkShop
    public async Task CloseShopAsync()
    {
         // signal that no more work is to be expected:
         this.WorkRequests.Complete();
         // await until the worker has finished his last job for the day:
         await this.FinishedWork.Completion();
    }
}

TODO: правильная реакция на CancellationToken.CancellationRequested
ТОДО: правильная реакция на исключения, создаваемые работой
TODO: решить, использовать ли несколько потоков для выполнения работы

FrontOffice имеет одну асинхронную функцию, которая принимает работу, отправляет ее в WorkRequests и ожидает завершения работы:

public async Task<IWork> OrderWorkAsync(IWork work, CancellationToken token)
{
    await this.WorkRequests.SendAsync(work, token);
    IWork finishedWork = await this.FinishedWork.ReceivedAsync(token);
    return finishedWork;
}

Итак, ваш процесс создал объект WorkShop и запускает один или несколько потоков, которые запустят StartWorking.

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

  • Создать объект, который содержит входные параметры и функцию DoWork
  • Обратитесь в мастерскую за FrontOffice
  • Ожидание OrderWorkAsync

.

class InformationGetter : IWork
{
     public int Id {get; set;}                     // the input Id
     public Data FetchedData {get; private set;}   // the result from Remoting.GetData(id);
     public void DoWork()
     {
         this.FetchedData = remoting.GetData(this.Id);
     }
}

Наконец, версия Async вашего пульта дистанционного управления

async Task<Data> RemoteGetDataAsync(int id)
{
     // create the job to get the information:
     InformationGetter infoGetter = new InformationGetter() {Id = id};

     // go to the front office of the workshop and order to do the job
     await this.MyWorkShop.FrontOffice.OrderWorkAsync(infoGetter);
     return infoGetter.FetchedData;
}
0 голосов
/ 01 июля 2018

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

Да, но когда вы застряли с API синхронизации, тогда Task.Run() может быть вашим меньшим злом, особенно на Клиенте.

Ваша текущая версия GetDataTakingLotsOfTime () на самом деле не является асинхронной. FromResult () просто помогает подавить предупреждение об этом.

А как насчет Task.FromResult? Могу ли я ожидать таких методов асинхронно в большем количестве, чем существует потоков?

Непонятно, откуда взялась ваша идея «количества потоков», но да, запуск метода Task и его последующее ожидание, по сути, запускает его в ThreadPool. Но Task.Run более понятен в этом отношении.

Обратите внимание, что это не зависит от модификатора async метода - async - это деталь реализации, вызывающий заботится только о том, чтобы он возвращал Task.

В настоящее время он ограничен числом потоков (которых четыре).

Это требует некоторого объяснения. Я не понимаю

...