Страницы

Поиск по вопросам

Показаны сообщения с ярлыком async-programming. Показать все сообщения
Показаны сообщения с ярлыком async-programming. Показать все сообщения

вторник, 17 марта 2020 г.

Sockets client+server with await/async c# 5.0

#c_sharp #async_programming #async_await


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

Проблема такова. Хочу написать простое асинхронное клиент-серверное приложение с
банальной передачей байт. Удалось найти и реализовать 2 решения: колбэки и await/async
базирующиеся на tcplistener и networkStream. Но это все не то. Пытаюсь написать базируясь
на Task, Sockets и async/await без tcplistener и networkStream. Почитал некоторую литературу,
но необходимого ответа/примера не нашел. 

Знаю, что делается это базируясь на следующем:

public static Task ConnectAsync(this Socket socket, EndPoint remoteEP) {
    return Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, remoteEP,
null);
}


но пока ничего не выходит. Даже сервер не могу "собрать" воедино. Прошу помощи.

Обновление

Удалось подключиться.А вот с передачей пока не получается.

private readonly Socket _server;
public ServerSocket(IPAddress ipAddress, int port) {
    _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    _server.Bind(new IPEndPoint(ipAddress, port));
    _server.Listen(20);
    Console.WriteLine("Wait connection");
    Accept();
}
private async void Accept() {
    for (;;) {
        var socket = await Task.Factory.FromAsync < Socket > (_server.BeginAccept,
_server.EndAccept, true);
        Console.WriteLine("Connected");
    }
}

    


Ответы

Ответ 1



Для начала, правильный путь — это именно async/await. По поводу работы с сокетами, вот есть хороший обзор. Итак, создаём серверный сокет: public ServerSocket(IPAddress ipAddress, int port) { _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _server.Bind(new IPEndPoint(ipAddress, port)); _server.Listen(20); } Здесь мы создали сокет, но основная работа у нас не мгновенна, поэтому выносим её в метод Run. Task AcceptAsync() { return Task.Factory.FromAsync( _server.BeginAccept, _server.EndAccept, null); } public async Task Run() { while (true) { var client = await AcceptAsync(); var clientServeTask = ServeClient(client); // что дальше делать с этим заданием - ваше дело // например, вы можете просто записать его в список } } ConnectAsync для сервера не нужен. В ServeClient инстанциируеем объект, представляющий клиента: async Task ServeClient(Socket client) { using (var c = new Client(client)) await c.RunAsync(); } В объект Client лежит собственно логика вашего сервера. byte[] greeting = Encoding.UTF8.GetBytes("Hi, I'm server ru.SO/2015"); public async Task RunAsync() { await SendAsync(greeting); var result = await ReceiveAsync(20); // ... } (Возможно, вам захочется вернуть результат.) Ну и вспомогательные процедуры: Task TrySendAsync(byte[] buffer, int offset, int size, SocketFlags flags) { return Task.Factory.FromAsync( _clientSocket.BeginSend, _clientSocket.EndSend, buffer, offset, size, flags, null); } Task SendAsync(byte[] buffer) { int sent = 0; int remaining = buffer.Length; while (remaining > 0) { var partSent = await TrySendAsync(data, sent, remaining, SocketFlags.None); sent += partSent; remaining -= partSent; } } Не забудьте закрыть клиентский сокет в Dispose. Да, наверняка вам захочется суметь оборвать работу сервера. Для этого вам стоит протянуть CancellationToken'ы через Task'и.

Ответ 2



Могу посоветовать пример из другого вопроса - https://ru.stackoverflow.com/a/516728/192004 . Там создается 10 клиентов, сервер поочередно принимает их и отправляет ответы. Сервер работает в одном потоке (точнее, в одной Task), клиенты могут работать в разных потоках. Всё на async/await + куча советов.

пятница, 13 марта 2020 г.

Несколько параллельных потоков

#c_sharp #wpf #async #async_programming


Есть класс для обработки пула задач. 

public class PoolManager
{
    List>> _listFunc { get; set; }
    List _listName { get; set; }
    public bool Active { get; set; } = false;
    public PoolManager()
    {
        _listFunc = new List>>();
        _listName = new List();
    }

    public async Task StartPool()
    {
        if (!Active)
        {
            Active = true;
            while (Active)
            {
                if (_listFunc.Count != 0)
                {
                    await Task.Run(async () =>
                    {
                        var fun = _listFunc[0];
                        RemoveById(0);
                        await fun();

                    });
                }
            }
        }
       return "";
    }

    public void Add(Func> func,string name)
    {
        _listFunc.Add(new Func> (func));
        _listName.Add(name);
    }

    public void Stop()
    {
        Active = false;
    }
}


Всё работает, но есть необходимость запускать несколько паралельных таких пулов.
запускаю я его так:

 if (!VP[0].Active)
 {
      await Task.Run(async () =>
      {
           await VP[0].StartPool();
      });
 }
 else VP[0].Stop();


Но таким образом я могу запустить только 1 такой поток. Как можно реализовать одновременный
запуск нескольких таких потоков с возможностью в последствии обращаться к ним? И соответственно
без блокировки главного потока.
    


Ответы

Ответ 1



Создавать список Task'ов не нужно. Просто надо указать AttachedToParent Task.Factory.StartNew(() => { for(var i=0; i < 10; i++) Task.Factory.StartNew(() => { /*... */}, TaskCreationOptions.AttachedToParent); }).Wait(); Внешний Task дождется завершение всех Task'ов, запущенных в цикле. Если в основном потоке не надо ждать завершения внешнего Task, то Wait не надо указывать. Но при этом этот Task будет ждать завершение работы остальных, запущенных с AttachedToParent. Если требуется передавать данные, то надо использовать классы, например, из System.Collections.Concurrent. Пример - тут.

пятница, 14 февраля 2020 г.

Task.Run - антипаттерн async/await? C#

#c_sharp #async_await #async #async_programming


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


  Как только код доходит до метода Task.Run(), достаётся другой поток из пула потоков
и в нём исполняется код, который мы передали в Task.Run(). Старый поток, как и положено
приличному потоку, возвращается в пул и ждёт, когда его снова позовут делать работу.
Новый поток выполняет переданный код, доходит до синхронной операции, синхронно выполняет
её (ждёт пока операция не будет выполнена) и идёт дальше по коду. Иными словами, операция
так и осталась синхронной: мы, как и раньше, используем поток во время выполнения синхронной
операции. Единственное отличие — мы потратили время на переключение контекста при вызове
Task.Run() и при возврате в ExecuteOperation(). Всё стало немножечко хуже.


Один из вопросов, который там рассматривается: вызов Task.Run - это антипаттерн,
и нужен он только для отзывчивости GUI.

Вопрос именно про Task.Run(() => _anyWork()), где _anyWork() содержит синхронный
код. То, что написано в статье, звучит достаточно логично, если делать так:

await DoWork();
...

Task DoWork() => Task.Run(_work);


Да, в таком случае создается лишняя нагрузка на пул потоков. Но, ведь если делать так:

var task1 = DoWork1();
var task2 = DoWork2();
var task3 = DoWork3();
await Task.WhenAll(task1, task2, task3);


Task.Run сразу превращается в нормальный код, ведь так? 

Поток, который будет выполнять этот код, создаст три других потока (upd: оговорился:
инициирует добавление работы в очередь, которая будет запущена в ThreadPool), которые
параллельно будут выполнять свою работу параллельно. Дальше, когда он встретит await
- он вернет управление (в итоге, скорее всего, вернется в пул). Исправьте, пожалуйста,
если не так.

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

Возможно есть какие-то официальные рекомендации MS? На msdn я нашел только сухое
описание работы методов.
    


Ответы

Ответ 1



Один из вопросов, который там рассматривается: вызов Task.Run - это антипаттерн, и нужен он только для отзывчивости GUI. Task.Run Method Ставит в очередь заданную работу для запуска в ThreadPool и возвращает задачу или дескриптор Task для этой работы. То есть Task.Run - это способ выполнить какую-то работу в пуле потоков с возможностью ожидания результата асинхронно. Когда это может понадобится? Возможные примеры использования: 1) Запуск IO/CPU нагрузки пуле потоков, чтобы не грузить основной UI поток (пример) 2) Запуск асинхронного кода синхронно из UI потока. Такое может потребоваться, когда у вас большое приложение и вы постепенно переходите на асинхронные вызовы вместо синхронных, но не везде пока можете вызывать ваш новый API асинхронно, пр этом вам надо избегать дедлоков в UI потоке, например Task.Run(()=>CallSmthgAsync().GetAwaiter().GetResult()).GetAwaiter().GetResult(); - это не оч красивый код и он должен быть исправлен, но не всегода можно внедрить асинхронные вызовы за один заход, потому я такое сам делал и видел иногда. Когда это НЕ надо: когда вас не заботит факт блокировки текущего потока какой то синхронной работой. В прмере в вопросе задача и так уже запущена в пуле потоков, потому переключаться в другой поток и там выполнять что то синхронное смысла не имеет. Но если бы та же работа была запущена из UI потока, то без Task.Run все UI приложение бы встало колом. Что касается библиотечного кода. Стройте ваш API и его реализацию исходя из требований и вариантов использования. Если у вас много I/O операций (работа с файлами, с сетью, с БД), то это по сути самой собой намекает на необходимость асинхронного API. Если вас смущает выбор между синхронным API и асинхронным, то реализуйте оба, пусть клиент выбирает,что ему нужно. Отсюда вывод: самое главное, чтобы не наломать дров, всегда знайте что вы делаете и зачем вы это делаете. Переключайте контекст тогда, когда у вас есть причина это делать. Если каждая строка вашего кода будет обоснована и иметь причину, почему она именно так написана, то проблем с выбором хороший код/ плохой код у вас будет гораздо меньше.

Ответ 2



Код метода *Async выполняется в текущем потоке до первого await. И текущий поток будет захвачен. Например, HttpClient до начала асинхронного запроса синхронно запрашивает dns, что может быть долгой операцией. И вы можете решить, что вас больше беспокоит - ожидание текущего потока или накладные расходы от Task.Run() (которые минимальны по факту, разве что раздувают код)

Ответ 3



использовать Task: если внутри нужно использовать асинхронную операцию: внутри их несколько синхронный код и асинхронная операция вот интересная статья о using(IDisposable) и потенциальном баге если ты делаешь свою собственную асинхронную операцию (например превращаешь событие в Task) - часто используют TaskCompletionSource второе похоже на I/O-bound операцию - не уверен, писать ли отдельным пунктом

пятница, 10 января 2020 г.

Туманности и мои пробелы в знаниях с async\await и Task'ами в целом

#c_sharp #async_await #async_programming #task #context


Здравствуйте, я захотел опробовать такую вкусняшку C# как async/await и написал тестовую
программу:

class MySynchronizationContext : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        Console.WriteLine("Post");
        base.Post(d, state);
    }
    public override void Send(SendOrPostCallback d, object state)
    {
        Console.WriteLine("Send");
        base.Send(d, state);
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext());
        var browsers = GetBrowsers();
        Console.WriteLine("start");
        Console.WriteLine(browsers.Result);
    }
    static async Task GetBrowsers()
    {
        var res = string.Empty;
        var bd = await GetFromFS();
        res += bd;               // <-тут могла быть операция с UI
        var net = await GetFromNet();
        res += " and " + net;   // <-тут могла быть операция с UI
        var cpu = await Task.Run(() => { Thread.Sleep(200); return "edge"; });
        res += " and " + cpu;   // <-тут могла быть операция с UI
        return res;
    }
    static async Task GetFromFS()
    {
        using (var sr = new StreamReader(@"C:\bd.txt"))
        {
            var res = await sr.ReadToEndAsync();
            // тут могла быть операция с UI
            return res + " and firefox";
        }
    }
    static async Task GetFromNet()
    {
        await Task.Delay(200);
        // тут могла быть операция с UI
        return "chrome";
    }

}


Я ожидал, что после каждого await'а мой контекст будет восстанавливаться (ведь будь
это приложение с UI, то надо же получать доступ к контролам), а значит, что после каждого
await'а в консоль будет выводится "Post", но каково было мое удивление когда вывелся
только 2 раза. Отсюда вопрос:


почему контекст привязывается не всегда?


Теперь к async/await: как я понимаю, начиная с var browsers = GetBrowsers(); вызов
будет проводится по стеку вниз до первого таска (в данном случае sr.ReadToEndAsync();,
а может и ниже), и управление сразу перейдет обратно в метод Main(), не дожидаясь завершения
этого таска; остальная часть async-методов будет выполняться после выполнения данного
таска и так далее, из чего возникает следующий вопрос:


кто ждет тот самый первый таск (затем следующие, когда до них дойдет
очередь после await'а), изменяет его состояние на
RanToCompletion? Думаю, было бы глупо думать, что для ожидания
выделяется поток из пула потоков, да?


Третий вопрос стоит на стыке тасков и пула потоков. Продолжение после await, как
я понимаю, выполняется в пуле потоков (там ему передается контекст), но...


что засовывает часть кода после await в пул потоков и когда (когда
таск завершился или когда создался?)?


Заранее спасибо за прояснение ситуации
    


Ответы

Ответ 1



Дело в том, что выполнение метода Post() в вашем контексте вы делегируете базовому методу. А базовый метод ставит делегат на выполнение в пуле потоков. В случае с UI такого не происходит, потому что, например, WinForms контекст полностью переопределяет методы. Вставим логирование контекста и текущего потока в ваш код: class MySynchronizationContext : SynchronizationContext { public override void Post(SendOrPostCallback d, object state) { Console.WriteLine($"Post, thread id = {Thread.CurrentThread.ManagedThreadId}"); base.Post(d, state); } public override void Send(SendOrPostCallback d, object state) { Console.WriteLine($"Send, thread id: {Thread.CurrentThread.ManagedThreadId}"); base.Send(d, state); } public override string ToString() { return "My"; } } public class Program { public static void Main(string[] args) { SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext()); var browsers = GetBrowsers(); Console.WriteLine("start"); Console.WriteLine(browsers.Result); } static async Task GetBrowsers() { LogCurrentContext("GetBrowsers prologue"); var res = string.Empty; var bd = await GetFromFS(); LogCurrentContext("GetBrowsers after GetFromFS"); res += bd; // <-тут могла быть операция с UI var net = await GetFromNet(); LogCurrentContext("GetBrowsers after GetFromNet"); res += " and " + net; // <-тут могла быть операция с UI var cpu = await Task.Run(() => { Thread.Sleep(200); return "edge"; }); LogCurrentContext("GetBrowsers after Task.Run"); res += " and " + cpu; // <-тут могла быть операция с UI return res; } private static void LogCurrentContext(string message) { Console.WriteLine($"{message}: {(SynchronizationContext.Current?.ToString() ?? "default")} context, thread id = {Thread.CurrentThread.ManagedThreadId}"); } static async Task GetFromFS() { LogCurrentContext("GetFromFS prologue"); using (var sr = new StreamReader(@"D:\GetEventsMarkets.sql")) { var res = await sr.ReadToEndAsync(); LogCurrentContext("GetFromFS after ReadToEndAsync"); // тут могла быть операция с UI return res + " and firefox"; } } static async Task GetFromNet() { LogCurrentContext("GetFromNet prologue"); await Task.Delay(200); // тут могла быть операция с UI LogCurrentContext("GetFromNet after Task.Delay"); return "chrome"; } } Чаще всего я получал подобный результат: GetBrowsers prologue: My context, thread id = 1 GetFromFS prologue: My context, thread id = 1 Post, thread id = 1 GetFromFS after ReadToEndAsync: default context, thread id = 3 start Post, thread id = 3 GetBrowsers after GetFromFS: default context, thread id = 3 GetFromNet prologue: default context, thread id = 3 GetFromNet after Task.Delay: default context, thread id = 4 GetBrowsers after GetFromNet: default context, thread id = 4 GetBrowsers after Task.Run: default context, thread id = 4 Здесь нам интересны две вещи. В кастомном контексте вызываются только прологи методов GetBrowsers() и GetFromFS(). После того, как выполняется строка await sr.ReadToEndAsync(), продолжение метода GetFromFS() постится в кастомный контекст. Это первый вызов Post(). Далее метод GetFromFS() завершается и продолжение метода GetBrowsers() снова постится в кастомный контекст. Это второй вызов Post(). Однако поскольку кастомный контекст просто запускает код в пуле потоков, эти продолжения работают уже в контексте пула потоков. Именно поэтому мы больше не видим вызовов кастомного контекста. Метод GetFromFS() начал исполняться в потоке с id=1. Однако само продолжение при этом выполнялось в потоке с id=3 по причине, описанной выше. Продолжение после вызова GetFromFS() было вызвано в этом же потоке (Post, thread id = 3). "Самый первый таск", как и любой другой, ждет специальный IO поток (т.н. IO completion port, IOCP). Но такие потоки ждут очень большое количество завершений, в т.ч. и тасков, поэтому говорить о "один таск -- один ждущий поток" не приходится. Нырнуть вглубь и почитать подробнее можно в статье на Хабре. Продолжение async метода выполняется в захваченном контексте. Если такого контекста нет (например, при вызове с ConfigureAwait(false)) -- продолжение выполняется в контексте пула потоков. Вызов продолжение в соответствующем контексте выполняется компилятором -- он генерирует соответствующий код с вызовом Post(). Компилятор разбирает async метод на составляющие (пролог+продолжения) и генерирует из них стейт-машину с переходами. Очень рекомендую посмотреть это выступление (или хотя бы слайды), а также ознакомиться с этим ответом. После этого фразы вроде "async/await занимается переключением контекста" должны пропасть из вашего обихода. Как резюме: вы получили смущающие результаты потому, что ваша реализация контекста, строго говоря, некорректна. По сути она аналогична контексту пула потоков, который используется для консольных приложений и в котором исполняются все продолжения, если не был обнаружен другой контекст.

Ответ 2



почему контекст привязывается не всегда? Проблема вашего контекста - в том, что он не умеет восстанавливать себя. Если вы пишите свой контекст - то вы сами должны позаботиться чтобы все продолжения запускались в нем же. кто ждет тот самый первый таск Если асинхронность правильная - то "самый первый" Task, как и все последующие, создается при помощи механизма TaskCompletionSource. Например, так (код привожу только для примера, в реальности надо еще исключения обрабатывать): Task VeryFirstTask() { var tcs = new TaskCompletionSource(); Action handler = null; handler = () => { tcs.SetResult(42); SomeEvent -= handler; }; SomeEvent += handler; return tcs.Task; } Не обязательно используются события - но идея одна и та же. Где-то сохраняется TaskCompletionSource, у которого в нужный момент вызывается SetResult/SetException/SetCanceled. что засовывает часть кода после await в пул потоков и когда (когда таск завершился или когда создался?)? Напрямую его туда засовывает контекст синхронизации. Опосредовано в этом участвует так же такая структура данных как TaskAwaiter (она хранит захваченный контекст синхронизации).

суббота, 4 января 2020 г.

Использование promise с циклом for

#javascript #nodejs #async_programming #promise


Задача: собираю данные геодаты с сервера (не моего). Для этого формирую запрос для
определенного промежутка координат и в цикле for отправляю запрос на сервер. Ответ
записываю в файл и дабы сервер не банил меняю ip адрес через tor-control. Ожидание
ответа реализовано с использованием promise. Это в идеале. На деле (судя по логам в
консоли) данные отправляются как попало. Подскажите где я повернул не туда.

for (var i = xMin; i <= xMax; i++) {
    for (var k = yMin; k <= yMax; k++) {
        var swp = swPoint(k, i);
        var nep = nePoint(k, i);
        var llsw = pointToLatLong(swp[0], swp[1]);
        var llne = pointToLatLong(nep[0], nep[1]);
        var datatosend = 'fromlat=' + llsw[0].toString() + 'tolat=' + llne[0].toString()
+ 'fromlng=' + llsw[1].toString() + 'tolng' + llne[1].toString();

        currX = k;
        currY = i;

        filename = "json/" + currX.toString() + "_" + currY.toString() + "_" + zoom.toString()
+ ".json";

        options.body = datatosend;

        console.log(filename);

        GetData(options)
        .then(body =>{
            return WriteToFile(body, filename);
        })
        .then(() =>{
            return GetNewCircuit();
        })
        .then(() =>{
            console.log('Step finished');
        })
    }
}


Судя по логам получается что он 4 раза проносится по циклу не дожидаясь выполнения
предыдущих операций. 

Текст лога консоли:

json/0_0_1.json
json/1_0_1.json
json/0_1_1.json
json/1_1_1.json
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished


Вызываемые методы:

function GetNewCircuit(){
    return new promise(function(resolve, reject){
        new_identity('127.0.0.1', 9051, cookie, function(err){
            if(!err){
               console.log('Circuit changed');
               resolve()
            } else{
                console.log(err);
                reject(err);
            }
        });
    });
}

function GetData(reqOptions){
    return new promise(function(resolve, reject){
        request(reqOptions, function(error,response,body){
            if(!error && response.statusCode == 200){
                console.log('Get response');
                resolve(body);
            }
            else{
                reject(error);
            }
        });
    });
}

function WriteToFile(dataToWrite, fileToWrite){
    return new promise(function(resolve, reject) {
        fs.writeFile(fileToWrite, dataToWrite, function(err) {
           if(!err){
               console.log(fileToWrite);
               resolve();
           }
           else{
               reject(err);
           }
        }); 
    });
}


С js'ом знакомство только начинаю, поэтому если кроме ответа еще и посоветуете где
и как лучше ознакамливаться буду только рад.
    


Ответы

Ответ 1



В коде этом сразу две основных проблемы (из трех) начинающих javascript-разработчиков вижу я. Проблема один - непонимание асинхронности. Разумеется, цикл не ждет возврата результата асинхронной операции, чтобы прокрутиться дальше. Представьте что у вас четыре гиперактивных двортерьера, и вы подряд кидаете им четыре мячика. В каком порядке они их принесут обратно? Да черт его знает. А то что внутри Promise.then, это колбек, только написанный удобнее. Что делать? Не надо: Первое решение которое приходит в голову, а давайте дождемся конца первой операции, а потом уже счетчик цикла увеличим и в следующей итерации запустим следующую операцию. Но этим Вы просто превращаете асинхронную операцию в синхронную и теряете весь профит от асинхронности. Надо: Понять что основная проблема этого кода не в асинхронности, а в том что Вы не понимаете замыкания. Ну то есть вас не смущает что у вас консольложится json/1_1_1.json столько раз? Проблема два: непонимание областей видимости в js, они же замыкания. Вопрос про потерю значения переменной в цикле - топ 1 вопрос на собеседованиях на мидл разработчика, при неответе на который разговор можно сворачивать. На этом ресурсе этот вопрос в разных формулировках встречается пару раз в неделю. Например вот здесь на него подробно ответили (не упоминая block scope в es6 правда). Ну то есть ядро ошибки в вашем коде выглядит как-то так: for(var i = 0; i<4; i++){ setTimeout(function(){console.log(i)}, 1000) } setTimeout тут - как пример простейшей асинхронной операции. Если Вы не понимаете почему тут выведет 4 раза 4 - надо медитировать пока не поймете. Обязательно. Если вкратце, то это происходит потому что скоуп в js для var переменных - это функция, а не фигурные скобочки. Цикл отдельного скоупа не образует и все четыре раза функция будет ссылаться на одну и ту же переменную i, на одну и ту же область памяти. Ок, я все понял, все равно хочу синхронно. Для того чтобы хитро управлять ходом управления множества асинхронных запросов есть ряд распространенных библиотек, например async, посмотрите например на async.waterfall

Ответ 2



Собери промисы в массив в цикле потом дождись их выполнения используя Promise.all

пятница, 20 декабря 2019 г.

Непонятности с асинхронным кодом

#c_sharp #асинхронность #async #async_programming #async_await


Читаю вот эту статью про использование Task и async-await в C# 
В ней приведён такой код

var client = new WebClient();
var task = client.DownloadStringTaskAsync("/api/blabla");
Console.WriteLine("Hello world!");
var result = await task;
Console.WriteLine("Got some data");


и далее про него говорится вот что


  Почему этот код хороший и правильный? Потому что DownloadStringTaskAsync возвращает
Task, который инкапсулирует операцию ввода-вывода — то есть I/O bound операцию. И практически
весь ввод-вывод является асинхронным — то есть, для его осуществления, нигде, начиная
с самого верхнего уровня вызова метода DownloadStringTaskAsync и заканчивая драйвером
сетевой карты, абсолютно нигде не нужен дополнительный поток, который будет «ждать»
или «обрабатывать» эту операцию.


Точно так же приводится пример из JS/jQuery

$.get('/api/blabla', function(data) {
  console.log("Got some data.");
});
console.log("Hello world!")


и комментарий к нему 


  Это — очень яркий пример асинхронного программирования. Здесь нет никакой многопоточности
— Javascript строго однопоточен


В обоих случаях особо отмечается что многопоточности тут нет. И подобное я читал
также и в других источниках что при использовании async-await (и асинхронности вообще)
далеко не всегда подразумевает использование дополнительного потока. И это мне совершенно
непонятно.  Возьмем тот же пример на jQuery. Сначала выполняется запрос, у которого
есть коллбэк а потом выводится сообщение "Hello world!". Но так как запрос выполняется
асинхронно то "Hello world!" выводится раньше. И мне непонятно как тут обойтись без
нескольких потоков? Ведь если асинхронный запрос выполняется в одном потоке с основным,
то по идее и асинхронности быть не должно потому что поток сначала должен дождаться
выполнения запроса а только потом вывести 
"Hello world!". То же самое и по C# коду. Объясните пожалуйста почему автор так упорно
говорит что многопоточности тут нет? Заранее спасибо!
    


Ответы

Ответ 1



Дело в том, что сетевые и дисковые вызовы в Windows поддерживают так называемый Overlapped I/O, который позволяет получить уведомление по завершению операции. Т.е. во время выполнения операции с диском или сетью пользовательский поток может делать все что угодно, а не обязательно спать и ждать результатов. Как работает обычное, неасинхронное чтение: Ваш код делает синхронный вызов Поток, сделавший вызов, засыпает Данные читаются из диска или из сети Поток просыпается Вызов возвращает результат Как работает асинхронное чтение Ваш код делает асинхронный вызов Поток, сделавший вызов, освобождается - возвращается в пул Данные читаются из диска или из сети Приложение получает уведомление от системы о получении данных (через I/O Completion Port) Рантайм находит код, который должен обработать результат - продолжение вашего async-метода и ставит его на выполнение. Ваш код возвращает результат Разница в том, что между 2 и 6 в асинхронном коде никакой поток не спит в ожидании результата. А потоки в Win - это достаточно ценный ресурс. Их тяжело (относительно тяжело) запустить. Они требуют памяти (около 1Mb на поток). Их надо синхронизировать - чем больше потоков, тем больше накладных расходов. Кроме того, если между 2 и 6 в вашем коде есть что-то, что можно выполнить (например, вы не сразу потребовали результат чтения с диска или из сети) - то это будет выполнено потоком, вместо пустого ожидания. При этом будет одновременность выполнения вашего кода, и физического чтения данных из сети. Но многопоточности все еще не будет - поток то у вас останется ровно один. То, что при этом драйвер сетевой карты читает данные - не добавляет еще один поток в ваше приложение. И вы можете спокойно обойтись без синхронизации, и прочих неприятностей, связанных с многопоточностью. Абстракция, как же без нее: многопоточное изготовление пиццы - склонировать себя и заставить клона готовить пиццу. по завершению - слиться в одного человека. асинхронное изготовление пиццы - позвонить в доставку, спокойно делать свои дела, по звонку в дверь открыть и забрать готовую пиццу.

Ответ 2



Многопоточности нет потому, что ожидание загрузки в DownloadStringTaskAsync не занимает отдельного потока! Происходит следующее: Запускается DownloadStringTaskAsync, отправляется GET-запрос. Из DownloadStringTaskAsync возвращается неоконченный Task. Когда-нибудь приходит ответ на GET-запрос, в этот момент Task переходит в состояние «окончен». await получает управление, присваивает результат. Между пунктами 2 и 3 код бежит в никаком потоке: он представлен лишь структурами данных в памяти, но не занимает никакой поток.

воскресенье, 15 декабря 2019 г.

Как поставить вызовы асинхронной ф-ии в очередь, и не чаще 3 в секунду?

#javascript #алгоритм #async_programming


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

Есть ограничение: нельзя вызывать эту ф-ю чаще 3 раз в секунду.

Не могу сообразить, как проще сделать очередь задач.

Сделал два буфера: массив времён последних трёх вызовов times и массив объектов заданий
tasks, где каждый содержит параметры вызова и callback.

Перед выполнением очередного вызова необходимая задержка определяется как неотрицательная
разность текущего времени c моментом times[0]. После выполнения вызова, текущее время
заносится в хвост: times.push(ts); times = times.slice(-3).

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

Есть ли в каком-нибудь JS фреймворке похожий функционал, чтобы списать, и правильно
ли я подхожу к задаче перестроения асинхронных вызовов в последовательность?

App.timeSpan = 1000;
App.times = [];
App.tasks = [];

// точка входа
App.api = function( method, params, callback) {
    var toWait, dt, ts = (new Date()).getTime();

    this.tasks.push({
        method: method,
        params: params,
        callback: callback
    });

    if( this.tasks.length == 1) {
        if( this.times.length > 2) {
            dt = ts - this.times[0];
            toWait = ( dt < this.timeSpan) ? this.timeSpan - dt : 0;
        } else {
            toWait = 0;
        }
        window.setTimeout(this.execute.bind(this), toWait);
    }
}

App.execute = function(){
    if( this.tasks.length == 0) return;

    // вызов ТОЙ ф-ии
    VK.api( this.tasks[0].method, this.tasks[0].params, this.executed.bind(this));
}

App.executed = function(r){
    var ts = (new Date()).getTime() // timestamp in seconds
        ,dt
        ,toWait
    ;

    this.times.push( ts);
    this.times = this.times.slice(-3);

    if( this.times.length > 2) {
        dt = ts - this.times[0];
        toWait = ( dt < this.timeSpan) ? this.timeSpan - dt : 0;
    } else {
        toWait = 0;
    }

    if( this.tasks.length > 1) {
        window.setTimeout(this.execute.bind(this), toWait);
    }

    this.tasks.shift().callback.call(this, r);
}

    


Ответы

Ответ 1



По-моему, радикально упростить код не получится. Получилось лишь сделать его чуть более "красивым": //for tests function mock(m, p, callback) { var time = Math.floor(Math.random() * 300); setTimeout(function() { callback(m + ' ' + p); }, time); } //for tests function callbackMock(r) { console.log("Callbacked: " + r); } function App() { } App.callsPerTimeSpan = 3; App.timeSpan = 1000; App.times = []; App.tasks = []; App.handling = false; App.taskHandler = mock; //mock - for tests App.api = function(method, params, callback) { this.tasks.push({ method: method, params: params, callback: callback }); if (!this.handling) { this.handling = true; this.execute(); } }; App.execute = function() { if (this.tasks.length == 0) { this.handling = false; return; } var toWait = 0; if (this.times.length == this.callsPerTimeSpan) { var ts = this.getCurrentTime(); var dt = ts - this.times[0]; toWait = (dt < this.timeSpan) ? this.timeSpan - dt : 0; } var task = this.tasks[0]; var that = this; window.setTimeout(function() { that.taskHandler(task.method, task.params, that.executed.bind(that)); }, toWait); }; App.executed = function(r) { var ts = this.getCurrentTime(); this.times.push(ts); this.times = this.times.slice(-this.callsPerTimeSpan); var task = this.tasks.shift(); var that = this; setTimeout(function() { task.callback.call(that, r); }, 0); //async call this.execute(); }; App.getCurrentTime = function() { return (new Date()).getTime(); }; //for tests for (var i = 0; i < 10; i++) { App.api(String.fromCharCode(i + 65), i, callbackMock); } "Красота" в первую очередь включает в себя отсутствующий дублированный код расчета времени и исполнения следующей задачи. Пример с выводом отладочной информации в fiddle.

Ответ 2



Есть ли в каком-нибудь JS фреймворке похожий функционал Есть. В jQuery точно есть throttle и debounce, в других тоже что-то похожее. Вообще, гуглится замечательно. Да и реализуется несложно. Например, вот расширенная версия с комментариями. там «проглатываются» лишние вызовы функции. В моей задаче этого нельзя допускать Тогда так: function queue(f, ms) { var calls = null; return function () { if (calls == null) { calls = []; setTimeout(function go() { // Гарантируем асинхронный вызов if (calls.length) { f.apply(null, calls.shift()) setTimeout(go, ms); } else { calls = null; } }); } calls.push(arguments); } } Проверка: f = queue(function () { console.log(new Date, arguments) }, 1000) f(1); f(1, 2); f({}); setTimeout(f, 3500); setTimeout(f, 4500, [4, 5], "q"); setTimeout(f, 6000, 6); setTimeout(f, 8000, 8); setTimeout(f, 8500, 8.5); setTimeout(f, 9000, 9); setTimeout(f, 11000, 9);

среда, 27 ноября 2019 г.

Как работают await async [дубликат]

#c_sharp #async_await #async_programming



    На данный вопрос уже ответили:
    
        
            Нужен async/await или не нужен?
                
                    5 ответов
                
        
    
    
Прочитал много литературы но пока никак не могу понять как работает await и async.
Ну хоть убейте. Везде примеры с httpclient, но для меня они не понятны. Пытаюсь разобраться сам.
Вот что я понял:


  Как только наш код встречает await
  происходит возврат управления. После
  завершения ожидаемой операции метод
  восстанавливается. Точнее продолжает
  выполнение с того места, на котором
  остановился, когда столкнулся с await.


Хорошо, я написал пару строк кода(возможно просто что-то сделал не так)

async Task myMethod() {
    int sum = 0;
    await SomeCycleAsync();
    Console.WriteLine("выполнился цикл2");
}
async Task SomeCycleAsync() {
    var myTask = await ResultOfCycle();

    Console.WriteLine("выполнился цикл1");
}
async Task < int > ResultOfCycle() {
    int sum = 0;
    for (int i = 0; i < 1000000000; i++) {
        sum += i;
    }
    return sum;
}

private void Form1_Load(object sender, EventArgs e) {
    myMethod();
}



В методе myMethod встречается слово await и, на сколько я понимаю, управление должна
перейти обратно в form_load, верно?
Во время выполнения метода SomeCycleAsync встречается await, т.е. по логике управление
должно перейти к Console.WriteLine("выполнился цикл2"); Но результат работы такой:



  выполнился цикл1
  
  выполнился цикл2


Объясните мне пожалуйста почему? Совсем не понимаю
    


Ответы

Ответ 1



Смотрите. Сами по себе async/await не включают таинственным образом многопоточность/асинхронность. Они лишь создают условия, при которых эту самую асинхронность легко реализовать. На самом деле, когда вызывается async-метод, происходит следующее. Начинает синхронно выполняться async-метод. Если этот метод заканчивается до первого await, результат доставляется синхронно, и из метода возвращается уже закончившийся, завершённый Task. Если в процессе выполнения встретился await, система проверяет, отработало ли уже задание, на которое вызывался await. Если это задание отработало, то подставляется его результат, и синхронное выполнение продолжается дальше. Если задание, на которое происходит await, ещё не отработало, в этот момент из метода возвращается незавершённый Task. В этой точке внешний код получает управление и продолжает выполняться. Например, этот код может записать Task в переменную и продолжать заниматься своими делами. Или он может выполнить await на полученный Task, и поскольку этот Task ещё не завершён, внешний код в этот момент аналогично отдаст управление ещё более внешнему коду, и. т. д. Когда Task, на который происходит await, завершится (произведя результат или исключение), код после await возобновит свою работу. В вашем случае происходит следующее: Вызывается Form1_Load. Выполняется строчка myMethod();. Этот код произведёт Task, который впоследствии будет просто проигнорирован. Начинает выполняться код метода myMethod. Выполняется синхронно int sum = 0;. Для выполнения следующей строчки для начала нужно выполнить метод SomeCycleAsync а затем await на результирующий Task. Начинается выполнение SomeCycleAsync. Для получения Task'а, по которому нужно делать await, запускается метод ResultOfCycle. Начинается выполнение ResultOfCycle(). Поскольку нигде в нём нету асинхронных вызовов, он выполняется полностью синхронно. Из метода возвращается завершённый Task. Управление возвращается в SomeCycleAsync. Выполняется await на Task, полученный в предыдущем пункте. Поскольку этот Task уже завершён, в переменную myTask просто записывается int-результат. Выполняется строчка Console.WriteLine("выполнился цикл1");. На этом выполнение метода SomeCycleAsync оканчивается. Поскольку в нём не было асинхронного ожидания, возвращается завершённый Task. Управление возвращается в метод myMethod(). Начинает выполняться await на полученный Task. Поскольку Task завершён, ничего не происходит, метод продолжает синхронно выполняться. Срабатывает строчка Console.WriteLine("выполнился цикл2");, метод заканчивается, возвращая завершённый Task. Управление возвращается в Form_Load. Полученный Task игнорируется, выполнение завершается. Для ваших целей правильнее было бы явно запускать вычисления асинхронным образом. Например, так: async Task myMethod() { await SomeCycleAsync(); Console.WriteLine("выполнился цикл-2"); } async Task SomeCycleAsync() { Console.WriteLine("стартует цикл"); // это запускает длинное вычисление на пуле потоков var result = await Task.Run(ResultOfCycle); Console.WriteLine("выполнился цикл, результат: " + result); } int ResultOfCycle() { int sum = 0; for (int i = 0; i < 1000000000; i++) sum += i; return sum; } private async void Form1_Load(object sender, EventArgs e) { await myMethod(); }

Ответ 2



У тебя ResultOfCycle() должна вернуть Task, но возвращает sum, которая является просто int. В самом методе нету второго потока, в котором он бы выполнялся. Await пишется перед объектом Task или методом, возвращающим объект Task. Пример для Task(вызов функции, которая ничего не возвращает): static void Main(string[] args) { My(); for (int i = 1; i <= 10; i++) { Thread.Sleep(1000); Console.WriteLine($"* {i*1000}"); } Console.ReadLine(); } static async void My() { await GetMessage(3000); } static Task GetMessage(int time) { return Task.Run(() => { Thread.Sleep(time); Console.WriteLine($"zxzxz {time.ToString()}"); }); } Пример для Task(вызов функции, которая возвращает объект типа T или в нашем случае string): static void Main(string[] args) { My(); for (int i = 1; i < 10; i++) { Thread.Sleep(1000); Console.WriteLine($"* {i*1000}"); } Console.ReadLine(); } static async void My() { string message = await GetMessage(3000); Console.WriteLine(message); } static Task GetMessage(int time) { return Task.Run(() => { Thread.Sleep(time); return $"zxzxz {time.ToString()}"; }); }

среда, 19 июня 2019 г.

Контроль порядка выдачи результатов асинхронной операции

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

Результат загрузки:

Здесь люди уже поднимали и решили похожую проблему, но я не знаю, как применить эти идеи в моем случае, когда для каждого превью динамически создаются элементы таблицы. Прошу помощи.
Мой код:
JavaScript
$(document).ready(function () {
$("#fileUpload").on('change', function () {
//Get count of selected files var countFiles = $(this)[0].files.length; var imgPath = $(this)[0].value; var extn = imgPath.substring(imgPath.lastIndexOf('.') + 1).toLowerCase(); var image_holder = $("#image-holder"); image_holder.empty(); if (extn == "gif" || extn == "png" || extn == "jpg" || extn == "jpeg") { if (typeof(FileReader) != "undefined") { //loop for each file selected for uploaded.
var newElem = document.createElement('table'); newElem.id = 'tl'; newElem.align = 'center'; newElem.border = 0;
for (var i = 0; i < countFiles; i++) { var reader = new FileReader();
reader.onload = function (e) {
var newRow = newElem.insertRow(0); var newCell1 = newRow.insertCell(0); newCell1.innerHTML = ""; var newCell2 = newRow.insertCell(0); newCell2.innerHTML = ""; var newCell3 = newRow.insertCell(0); newCell3.innerHTML = ""; var newCell4 = newRow.insertCell(0);
$("", { "src": e.target.result, "class": "thumb-image" }).appendTo(newCell4);
};
document.getElementById("image-holder").appendChild(newElem); reader.readAsDataURL($(this)[0].files[i]); image_holder.show(); } } else { alert("This browser does not support FileReader."); } } else { alert("Please select images only"); } }); });
HTML:




Ответ

Для организации последовательной загрузки можно использовать Promise
Для этого загрузку изображения с помощью FileReader, можно вынести в функцию, которая будет возвращать Promise, который разрешится тогда, когда картинка загрузится.
Она может выглядеть так:
function loadImage(image){ return new Promise(function(resolve, reject){ var fileReader = new FileReader(); fileReader.onload = function(e){ resolve(e.target.result); } fileReader.readAsDataURL(image); }); }
Используя функцию then можно подписаться на событие, которое произойдет, когда Prmoise перейдет в состояние готов.
например так:
loadImage().then(function(imageAsDataUrl){ ... });
Таким образом можно собрать цепочку асинхронных операций, которые будут выполняться друг за другом.
Итоговый вид может быть таким:
var queue = Promise.resolve();
[].reduce.call(this.files,function(queue, file, index){ return queue.then(function(){ return loadImage(file).then(function(imageAsDataUrl){ var newRow = newElem.insertRow(0); var newCell1 = newRow.insertCell(0); newCell1.innerHTML = ""; var newCell2 = newRow.insertCell(0); newCell2.innerHTML = ""; var newCell3 = newRow.insertCell(0); newCell3.innerHTML = ""; var newCell4 = newRow.insertCell(0);
$("", { "src": imageAsDataUrl, "class": "thumb-image" }).appendTo(newCell4); }); }); }, Promise.resolve()).then(function(){ // все картинки загрузились image_holder.show(); })
небольшое отступление: $(this)[0] это то же самое, что и this, можете проверить с помощью следующего выражения $(this)[0] === this вернет true

понедельник, 3 июня 2019 г.

Несколько параллельных потоков

Есть класс для обработки пула задач.
public class PoolManager { List>> _listFunc { get; set; } List _listName { get; set; } public bool Active { get; set; } = false; public PoolManager() { _listFunc = new List>>(); _listName = new List(); }
public async Task StartPool() { if (!Active) { Active = true; while (Active) { if (_listFunc.Count != 0) { await Task.Run(async () => { var fun = _listFunc[0]; RemoveById(0); await fun();
}); } } } return ""; }
public void Add(Func> func,string name) { _listFunc.Add(new Func> (func)); _listName.Add(name); }
public void Stop() { Active = false; } }
Всё работает, но есть необходимость запускать несколько паралельных таких пулов. запускаю я его так:
if (!VP[0].Active) { await Task.Run(async () => { await VP[0].StartPool(); }); } else VP[0].Stop();
Но таким образом я могу запустить только 1 такой поток. Как можно реализовать одновременный запуск нескольких таких потоков с возможностью в последствии обращаться к ним? И соответственно без блокировки главного потока.


Ответ

Создавать список Task'ов не нужно. Просто надо указать AttachedToParent
Task.Factory.StartNew(() => { for(var i=0; i < 10; i++) Task.Factory.StartNew(() => { /*... */}, TaskCreationOptions.AttachedToParent); }).Wait();
Внешний Task дождется завершение всех Task'ов, запущенных в цикле. Если в основном потоке не надо ждать завершения внешнего Task, то Wait не надо указывать. Но при этом этот Task будет ждать завершение работы остальных, запущенных с AttachedToParent.
Если требуется передавать данные, то надо использовать классы, например, из System.Collections.Concurrent. Пример - тут

пятница, 2 ноября 2018 г.

Непонятности с асинхронным кодом

Читаю вот эту статью про использование Task и async-await в C# В ней приведён такой код
var client = new WebClient(); var task = client.DownloadStringTaskAsync("/api/blabla"); Console.WriteLine("Hello world!"); var result = await task; Console.WriteLine("Got some data");
и далее про него говорится вот что
Почему этот код хороший и правильный? Потому что DownloadStringTaskAsync возвращает Task, который инкапсулирует операцию ввода-вывода — то есть I/O bound операцию. И практически весь ввод-вывод является асинхронным — то есть, для его осуществления, нигде, начиная с самого верхнего уровня вызова метода DownloadStringTaskAsync и заканчивая драйвером сетевой карты, абсолютно нигде не нужен дополнительный поток, который будет «ждать» или «обрабатывать» эту операцию.
Точно так же приводится пример из JS/jQuery
$.get('/api/blabla', function(data) { console.log("Got some data."); }); console.log("Hello world!")
и комментарий к нему
Это — очень яркий пример асинхронного программирования. Здесь нет никакой многопоточности — Javascript строго однопоточен
В обоих случаях особо отмечается что многопоточности тут нет. И подобное я читал также и в других источниках что при использовании async-await (и асинхронности вообще) далеко не всегда подразумевает использование дополнительного потока. И это мне совершенно непонятно. Возьмем тот же пример на jQuery. Сначала выполняется запрос, у которого есть коллбэк а потом выводится сообщение "Hello world!". Но так как запрос выполняется асинхронно то "Hello world!" выводится раньше. И мне непонятно как тут обойтись без нескольких потоков? Ведь если асинхронный запрос выполняется в одном потоке с основным, то по идее и асинхронности быть не должно потому что поток сначала должен дождаться выполнения запроса а только потом вывести "Hello world!". То же самое и по C# коду. Объясните пожалуйста почему автор так упорно говорит что многопоточности тут нет? Заранее спасибо!


Ответ

Дело в том, что сетевые и дисковые вызовы в Windows поддерживают так называемый Overlapped I/O, который позволяет получить уведомление по завершению операции. Т.е. во время выполнения операции с диском или сетью пользовательский поток может делать все что угодно, а не обязательно спать и ждать результатов.
Как работает обычное, неасинхронное чтение:
Ваш код делает синхронный вызов Поток, сделавший вызов, засыпает Данные читаются из диска или из сети Поток просыпается Вызов возвращает результат
Как работает асинхронное чтение
Ваш код делает асинхронный вызов Поток, сделавший вызов, освобождается - возвращается в пул Данные читаются из диска или из сети Приложение получает уведомление от системы о получении данных (через I/O Completion Port) Рантайм находит код, который должен обработать результат - продолжение вашего async-метода и ставит его на выполнение. Ваш код возвращает результат
Разница в том, что между 2 и 6 в асинхронном коде никакой поток не спит в ожидании результата. А потоки в Win - это достаточно ценный ресурс. Их тяжело (относительно тяжело) запустить. Они требуют памяти (около 1Mb на поток). Их надо синхронизировать - чем больше потоков, тем больше накладных расходов.
Кроме того, если между 2 и 6 в вашем коде есть что-то, что можно выполнить (например, вы не сразу потребовали результат чтения с диска или из сети) - то это будет выполнено потоком, вместо пустого ожидания. При этом будет одновременность выполнения вашего кода, и физического чтения данных из сети.
Но многопоточности все еще не будет - поток то у вас останется ровно один. То, что при этом драйвер сетевой карты читает данные - не добавляет еще один поток в ваше приложение. И вы можете спокойно обойтись без синхронизации, и прочих неприятностей, связанных с многопоточностью.

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

пятница, 26 октября 2018 г.

Как поставить вызовы асинхронной ф-ии в очередь, и не чаще 3 в секунду?

Асинхронная ф-я может вызываться из разных мест большого кода, с передачей коллбэка для возврата результата.
Есть ограничение: нельзя вызывать эту ф-ю чаще 3 раз в секунду.
Не могу сообразить, как проще сделать очередь задач.
Сделал два буфера: массив времён последних трёх вызовов times и массив объектов заданий tasks, где каждый содержит параметры вызова и callback.
Перед выполнением очередного вызова необходимая задержка определяется как неотрицательная разность текущего времени c моментом times[0]. После выполнения вызова, текущее время заносится в хвост: times.push(ts); times = times.slice(-3)
Ф-я вызывается с необх. таймаутом либо по поступлении первого объекта в пустую очередь, либо по завершении очередного задания из очереди.
Есть ли в каком-нибудь JS фреймворке похожий функционал, чтобы списать, и правильно ли я подхожу к задаче перестроения асинхронных вызовов в последовательность?
App.timeSpan = 1000; App.times = []; App.tasks = [];
// точка входа App.api = function( method, params, callback) { var toWait, dt, ts = (new Date()).getTime();
this.tasks.push({ method: method, params: params, callback: callback });
if( this.tasks.length == 1) { if( this.times.length > 2) { dt = ts - this.times[0]; toWait = ( dt < this.timeSpan) ? this.timeSpan - dt : 0; } else { toWait = 0; } window.setTimeout(this.execute.bind(this), toWait); } }
App.execute = function(){ if( this.tasks.length == 0) return;
// вызов ТОЙ ф-ии VK.api( this.tasks[0].method, this.tasks[0].params, this.executed.bind(this)); }
App.executed = function(r){ var ts = (new Date()).getTime() // timestamp in seconds ,dt ,toWait ;
this.times.push( ts); this.times = this.times.slice(-3);
if( this.times.length > 2) { dt = ts - this.times[0]; toWait = ( dt < this.timeSpan) ? this.timeSpan - dt : 0; } else { toWait = 0; }
if( this.tasks.length > 1) { window.setTimeout(this.execute.bind(this), toWait); }
this.tasks.shift().callback.call(this, r); }


Ответ

По-моему, радикально упростить код не получится. Получилось лишь сделать его чуть более "красивым":
//for tests function mock(m, p, callback) { var time = Math.floor(Math.random() * 300); setTimeout(function() { callback(m + ' ' + p); }, time); } //for tests function callbackMock(r) { console.log("Callbacked: " + r); } function App() { } App.callsPerTimeSpan = 3; App.timeSpan = 1000; App.times = []; App.tasks = []; App.handling = false; App.taskHandler = mock; //mock - for tests App.api = function(method, params, callback) { this.tasks.push({ method: method, params: params, callback: callback }); if (!this.handling) { this.handling = true; this.execute(); } }; App.execute = function() { if (this.tasks.length == 0) { this.handling = false; return; } var toWait = 0; if (this.times.length == this.callsPerTimeSpan) { var ts = this.getCurrentTime(); var dt = ts - this.times[0]; toWait = (dt < this.timeSpan) ? this.timeSpan - dt : 0; } var task = this.tasks[0]; var that = this; window.setTimeout(function() { that.taskHandler(task.method, task.params, that.executed.bind(that)); }, toWait); }; App.executed = function(r) { var ts = this.getCurrentTime(); this.times.push(ts); this.times = this.times.slice(-this.callsPerTimeSpan); var task = this.tasks.shift(); var that = this; setTimeout(function() { task.callback.call(that, r); }, 0); //async call this.execute(); }; App.getCurrentTime = function() { return (new Date()).getTime(); }; //for tests for (var i = 0; i < 10; i++) { App.api(String.fromCharCode(i + 65), i, callbackMock); }
"Красота" в первую очередь включает в себя отсутствующий дублированный код расчета времени и исполнения следующей задачи.
Пример с выводом отладочной информации в fiddle