Deutsch
Germany.ruФорумы → Архив Досок→ Программирование

Непонятно с async-await в C# - 2

1777  1 2 3 4 5 все
alex445 свой человек02.09.21 14:48
NEW 02.09.21 14:48 
в ответ AlexNek 02.09.21 13:31

А, понял. Если мы вызвали метод ConvertAsync, а он запустил другие задачи и их ожидает, то выполнение возвращается в вызвавший ConvertAsync код после первого же await в ConvertAsync. А вызвавший код, в свою очередь, либо ждёт, пока весь метод ConvertAsync не выполнится, либо просто забывает про этот метод (запустил и забыл). Соответственно, даже если после первого await внутри ConvertAsync выполнение в нём продолжается, вызвавший ConvertAsync код всё ещё либо ждёт, либо уже забыл.


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

#21 
alex445 свой человек02.09.21 15:32
NEW 02.09.21 15:32 
в ответ alex445 02.09.21 14:48, Последний раз изменено 02.09.21 15:33 (alex445)

Вобщем, с таской вызывающий код может сделать в основном 2 вещи - либо запустить её, либо ожидать, либо запустить и ожидать. Каких-то промежуточных вариантов, типа запустить и ожидать частичного выполнения (до какого-нибудь промежуточного await внутри этой таски) нельзя. Соответственно, либо вызывающий код ждёт всю таску до конца, либо "забывает" про неё.


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

#22 
Бесконечный цикл посетитель02.09.21 20:28
NEW 02.09.21 20:28 
в ответ alex445 02.09.21 15:32

Понять как эта штука работает можно только, если понять как она реализована через event loop. Таски это то, что добавляется в event loop и там их куча накаплинвается. Выполняются они одним потоком, который в явно заданные моменты может перескакивать на другики таски. Вот и все. Одинаково работает в ЖС, Питоне и др. языках. А из официальных доков и примеров ничего не понять. Просто надо понять когда таски добавляются, когда они подвешиваются, когда удаляются. И усе.


Одна event loop полезна, но весьма ограниченно. Иногда надо несколько event loops (каждая со своим потоком), либо просто еще workers с очередью. Вот тогда уже весело будет.

#23 
alex445 свой человек02.09.21 21:15
NEW 02.09.21 21:15 
в ответ Бесконечный цикл 02.09.21 20:28, Последний раз изменено 02.09.21 21:39 (alex445)

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


Или, как вариант, программисты слишком глубоко копают, что многим из них не нужно. Как я понял, с async/await важно знать только, что это необязательно выполняется в другом потоке (т.е. это не многопоток в чистом виде), и чего с ними можно делать, а чего нельзя. И несколько use cases. Лезть внутрь - вот зачем? Ну выкитал там в видео человек портянку какого-то странного кода. Я что, чтобы работать с async/await, должен эту и другие подобные портянки всегда в голове держать, и всегда прогонять свой код использования async/await через эти портянки - а что там внутри мол произойдёт? Проще иметь список абстрактных правил, если эти абстракции не сильно протекают. Ну это типа как просто водить машину, и уметь подшаманить там что-нибудь самому. Оно конечно полезно - уметь самому, но нахрена оно мне надо, если я программист, а нифика не автомеханик, ни слесарь, и у меня даже хобби такого нет - торчать под машиной? Вон джаваскриптеры вообще не знают, когда у них какого типа данные, и что получается при разных манипуляциях с ними - и ничё, нормально живут, бабки зарабатывают не меньше всяких шарперов, а работы и вакансий у них на порядок больше, чем у этих шарперов.


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


Но в любом случае, async/await не заменяет нормальную работу с потоками (Thread, lock, всякие MemoryBarrier и прочее). Даже, пожалуй, наоборот - если ты хорошо знаешь, как работать с потоками и писать потокобезопасный код, ты можешь вообще без async/await обходиться, а просто загонять всё явно в другие потоки и "вручную" синхронизировать их.


Я в своей программе делал проще. У меня в реальном времени с платы сбора данных поступали данные. Их надо было обсчитывать, надо было результаты обсчётов в реальном времени выводить на графики и обновлять показания в интерфейсе. Я сделал что-то вроде конвейера. Приходящие данные с платы записывались в массив в одном потоке - этот поток постоянно опрашивал порт и считывал из него новые данные. Другой поток брал сохранённые в предыдущий массив данные, делал первоначальную обработку и сохранял в другой массив. Тут не нужна была синхронизация и блокировки, т.к. один поток только писал в массив, а другой только читал из него же, отслеживая новые данные по размеру массива и последнего прочитанному индексу, и записывал результаты в следующий массив. Ну и так далее - было что-то около 3-4 таких "уровней". Ещё один поток брал данные из последнего массива, делал окончательный расчёт и сохранял в специальные структуры результатов - тоже в виде массива. Следующий поток данные из этих структур брал и обновлял интерфейс. И все эти потоки молотили постоянно - на каждом уровне обработки данных я опрашивал размер массива из предыдущего уровня - не увеличился ли он достаточно, чтобы набрать очередной пакет данных для расчёта. И я эти потоки разве что притормаживал через тупой Thread.Sleep(), т.к. постоянный опрос массива в бесконечном цикле естественно займёт ядро процессора на 100%.


Тогда я столкнулся с плохой производительностью графической системы из-за его так называемой retained model (я достаточно глубоко копаю?) и странной логикой отрисовок.

Там ещё ссылки есть на интересные статьи и видео

Introducing Direct2D | Microsoft Docs (искать по слову "retain")

Michael Wallent: Advent and Evolution of WPF | Charles | Channel 9 (msdn.com)


Конечно, это было костыльно. Но я тогда первый раз с многопоточностью работал, а надо было сделать не чтобы "на века" и "другие люди же будут пользоваться", а чтобы запустилось ещё вчера, т.к. завтра ехать на испытания в соседнее фермерское хозяйство. Я постепенно кое-где переписывал на async/await, где создание потоков было лишним, с блокировками (lock) повозился. Надо было вообще не опрашивать постоянно массивы, а что-то вроде событий организовать - мол, новых данных в массиве достаточно, чтобы набрался пакет для кванта расчёта (нужно было некое минимальное количество данных). Кое-где я и сделал что-то подобное. Но так до конца и не довёл - уже уезжал.

#24 
Murr патриот02.09.21 22:09
Murr
NEW 02.09.21 22:09 
в ответ alex445 02.09.21 21:15

Я что, чтобы работать с async/await, должен эту и другие подобные портянки всегда в голове держать, и всегда прогонять свой код использования async/await через эти портянки - а что там внутри мол произойдёт?

------

А чему тебя в "школе" учили?

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

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


если ты хорошо знаешь, как работать с потоками и писать потокобезопасный код, ты можешь вообще без async/await обходиться

-----

Разумеется. Бо, писали все что нужно когда не было async/await и в помине.

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


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

-----

Об а на...

Слыхал, федотыч, один - пишет, другой - читает... и ни синхронизации, ни блокировки не надо-ть... во как...

#25 
alex445 свой человек03.09.21 08:49
NEW 03.09.21 08:49 
в ответ Murr 02.09.21 22:09, Последний раз изменено 03.09.21 08:53 (alex445)
один - пишет, другой - читает... и ни синхронизации, ни блокировки не надо-ть... во как...

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


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


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


Там, где у меня что-то пересекалось, там я блокировал.

#26 
Программист коренной житель03.09.21 10:24
NEW 03.09.21 10:24 
в ответ alex445 03.09.21 08:49
Я имел ввиду, что один поток постоянно добавляет данные в массив - не изменяет сами данные. А друго поток только считывает новые данные. Их действия никак не пересекаются.

Очевидно, что они пересекаются как минимум на уровне массива. И именно там надо синхронизировать ;) Не зря же сделали Concurrent-контейнеры ;)


Но даже с твоей аналогией все не так очевидно ;)

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

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

В своей аналогии несколько грузчиков разгружают фуру и заполняют ленту и несколько рабочих уносят коробки на склад. В такой конструкции надо еще и блокировать доступ к ленте, так что без синхронизации никуда ;)

#27 
MrSanders коренной житель03.09.21 11:13
NEW 03.09.21 11:13 
в ответ Программист 03.09.21 10:24

А то подерутся за коробку, вазочку какую-нибудь разобьют...

#28 
AlexNek патриот03.09.21 13:07
AlexNek
NEW 03.09.21 13:07 
в ответ Бесконечный цикл 02.09.21 20:28
Таски это то, что добавляется в event loop и там их куча накаплинвается

Очень интересная интерпретация, а ссылочку на теорию мона? смущ

Или хотя бы кратко - как таски выживают в консольном приложении?

#29 
alex445 свой человек03.09.21 15:14
NEW 03.09.21 15:14 
в ответ AlexNek 03.09.21 13:07, Последний раз изменено 03.09.21 15:23 (alex445)

Вобщем, сейчас на всё есть готовые буквари, типа такого

How to: Implement a producer-consumer dataflow pattern | Microsoft Docs

А раньше я городил велосипеды.


Дело в том, в этом примере лента является синхронизируещем механизмом. Предлагаю убрать у ленты механику, т.е. вместо ленты у нас есть просто Х ячеек. При этом разгружающий фуру рабочий (а он должен быть один) должен класть коробки как можно ближе к себе, а рабочий склада (тоже один) тоже должен брать ближайшую к себе коробку. Получается такой FIFO контейнер. Так вот очевидно, что в момент, когда разгружающий фуру рабочий будет класть очередную коробку в ячейку, рабочий склада должен стоять и ждать ;)
В своей аналогии несколько грузчиков разгружают фуру и заполняют ленту и несколько рабочих уносят коробки на склад. В такой конструкции надо еще и блокировать доступ к ленте, так что без синхронизации никуда ;)

Вы под синхронизацией понимаете некое место с объектом синхронизации, типа lock(synchronisationObject)?


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


Массив был банальным списком. Про списки написано

List Class (System.Collections.Generic) | Microsoft Docs

It is safe to perform multiple read operations on a List, but issues can occur if the collection is modified while it's being read.

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


Если нет, то почему не потокобезопасно? Там внутри могут переаллокации старых данных происходить, если ты просто добавляешь данные в список? Но тогда сложность вставок не была бы О(1)

Collections and Data Structures | Microsoft Docs


Ещё раз - я при чтении не могу выйти за пределы списка, т.к. его размер постоянно проверяется. Изо всех "модификаций" списка - только добавление данных в конец. Всё. Всё равно не потокобезопасно читать? Штука в том, что у меня никогда проблем не было, если обращаться со списками таким образом - т.е. каких-то сбоёв или аномально прочитанных данных.

#30 
AlexNek патриот03.09.21 17:01
AlexNek
NEW 03.09.21 17:01 
в ответ alex445 03.09.21 15:14

Как то странно вы отвечаете, ну да ладно.

Я так понял, что практические примеры вам больше нравятся.

Давайте тогда ваш примерчик хотя бы в консольном исполнении.

#31 
alex445 свой человек03.09.21 19:22
NEW 03.09.21 19:22 
в ответ AlexNek 03.09.21 17:01, Последний раз изменено 03.09.21 19:41 (alex445)

Что-то типа такого накидал на .NET 5. Одним потоком пишем в лист случайное число данных в определённых пределах, другим читаем каждый раз фиксированное число НОВЫХ данных, если их набралось достаточно, нажимая на "у". Сделал паузу при добавлении данных в 3 секунды специально, чтобы если слишком часто нажимать на "у" ничего не читалось. Правда, клавиатурный воод буферизуется, поэтому все нажатия "у" запоминаются, даже если данных на данный момент не достаточно, и потом автоматом вводятся - следовательно, выводятся и значения из данных, даже если "у" больше не нажималось. Как это отключить - пока не придумал. Да и не требуется особо.


Операция чтения data.Count атомарная, насколько я понимаю, поэтому её чтение всегда даст число данных на текущий момент, как бы в список данные ни добавлялись. Да это и не важно - Count не может уменьшаться, а значит, я никогда не выйду за пределы реального числа данных в списке, кроме случая переполнения.


В таком предельно упрощённом случае работы потоков со списком блокировки и синхронизации не делаю - они не нужны. Если, конечно, там внутри при добавлении данных и чтении методами AddRange и GetRange не происходит каких-то потокобезопасных переаллокаций, который повлияют на порядок чтения данных из списка - например, поменяют индексы значений в списке. Но, судя по всему, такой дичи там не происходит - нелогично было бы, что при добавлении данных список бы внутри как-то передвигал значения уже хранящихся элементов или двигал их в памяти так, что при чтении клиент попадал бы на другие элементы. Ну и судя по работе программы, таких кульбитов не происходит. Может, они происходят очень редко?


Просто, на мой взгляд, в МСДНе дана общая рекомендация - список и его публичные методы не потокобезопасны в общем случае. А я предельно упростил, так что хоть список и не потокобезопасен, но по моей логике работы с его данныеми с ними ничего произойти не может.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static List<int> data = new();

        static int dataReadSize = 5;
        static int currentIndexToRead = 0;

        static Random r = new();

        static bool doWork = true;

        static void Main(string[] args)
        {
            Thread writeThread = new(new ThreadStart(WriteData));
            writeThread.IsBackground = true;
            writeThread.Start();

            Thread readThread = new(new ThreadStart(ReadData));
            readThread.IsBackground = true;
            readThread.Start();

            // to prevent app to end immediately
            while (doWork)
                Thread.Sleep(100);
        }

        static void WriteData()
        {
            while (doWork)
            {
                data.AddRange(
                    Enumerable.Range(
                        0,
                        r.Next(3, 9)));

                Thread.Sleep(3000); // imitate pause
            }
        }


        static void ReadData()
        {
            Console.WriteLine("Press 'y' to continue or any another key to stop.");

            while (doWork)
            {
                int howMuchCanRead = data.Count - currentIndexToRead;
                bool canRead = howMuchCanRead >= dataReadSize;

                if (canRead)
                {
                    if (Console.ReadKey(true).Key != ConsoleKey.Y)
                        doWork = false;

                    var buffer = data.GetRange(currentIndexToRead, dataReadSize);
                    currentIndexToRead += dataReadSize;

                    Console.WriteLine(string.Join(',', buffer));
                }
            }
        }
    }
}
#32 
AlexNek патриот04.09.21 10:35
AlexNek
NEW 04.09.21 10:35 
в ответ alex445 03.09.21 19:22

Ну закон сохранения энергии и тут работает. спок

А теперь тоже самое, но с маленьким дополнением: количество элементов массива-буфера не должно превышать N.

#33 
alex445 свой человек04.09.21 11:27
NEW 04.09.21 11:27 
в ответ AlexNek 04.09.21 10:35, Последний раз изменено 04.09.21 11:36 (alex445)
Ну закон сохранения энергии и тут работает. спок

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


А теперь тоже самое, но с маленьким дополнением: количество элементов массива-буфера не должно превышать N.

Так я и так не читаю больше, чем dataReadSize.


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


while (_doWork)
{
    Thread.Sleep(30); // to not keep the thread busy all time, and to allow TcpClient accumulate sufficient amount of data

    if (_client.Available > 0)
    {
        _buffer = new byte[_client.Available]; // _client is of type TcpClient
        _stream.Read(_buffer, 0, _buffer.Length); // _stream is of type NetworkStream
        RawData.AddRange(_buffer); // RawData is of type List<byte>
    }
}
#34 
AlexNek патриот04.09.21 11:41
AlexNek
NEW 04.09.21 11:41 
в ответ alex445 04.09.21 11:27
Так я и так не читаю больше, чем dataReadSize.

Нет, data.Count <=N


что при таком подходе к списку можно без синхронизаций и блокировок обойтись?

Если оставить за кадром всё остальное, то да.

#35 
Бесконечный цикл посетитель04.09.21 21:49
NEW 04.09.21 21:49 
в ответ AlexNek 03.09.21 13:07, Последний раз изменено 04.09.21 21:51 (Бесконечный цикл)
Очень интересная интерпретация, а ссылочку на теорию мона?

Это не интерпретация - это основы. А интерпретация это как реализовано с C#, Python или других языках или либах. Поэтому я и говорю, что лучше все-таки разобраться в основах, чем как это реализовано в каком-то языке. По крайней мере когда я въезжал в тему, мне так было существенно быстрее.


Вот ссылки: Coroutine, Event_loop


Смысл простой:

  • Корутины (например, объявляются через async) нельзя вызвать и получить значение. Единственно что можно это добавить ее как таск в очередь и поучить назад объект, который эту задачу представляет (фьючерс, промис и т.п.)
  • Внутри корутины можно ожидать (например, через await) когда другая таска родит завершится и получить ее результат. Это явный сигнал диспетчеру.


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


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

#36 
Бесконечный цикл посетитель04.09.21 22:17
04.09.21 22:17 
в ответ alex445 02.09.21 21:15
Но в любом случае, async/await не заменяет нормальную работу с потоками (Thread, lock, всякие MemoryBarrier и прочее).

Ну понятно что не заменяет - это разные модели.


Даже, пожалуй, наоборот - если ты хорошо знаешь, как работать с потоками и писать потокобезопасный код, ты можешь вообще без async/await обходиться, а просто загонять всё явно в другие потоки и "вручную" синхронизировать их.

Для async/await (в базовой модели) нужен только один поток, поэтому про синхронизацию и потоки можно спокойно забыть (что в ЖС и происходит). А реализовать самому конечно можно, чем люди десятки лет занимались. Например, на чем поднялся nginx? Правильно, на неблокировочных операциях. Это значит что может быть тысячи активных соединений (таски в очереди) и один рабочий поток со всем справляется, когда у стандартного Апачи есть несколько рабочих потоков и он скрипит от напряга, поскольку потоки блокируются.


У меня в реальном времени с платы сбора данных поступали данные.

Это вообще другая тема. Тут надо посмотреть в сторону стриминга: ReactiveX, например, для C# GitHub - dotnet/reactive: The Reactive Extensions for .NET


#37 
alex445 свой человек05.09.21 08:17
NEW 05.09.21 08:17 
в ответ AlexNek 04.09.21 11:41, Последний раз изменено 05.09.21 08:59 (alex445)
Так я и так не читаю больше, чем dataReadSize.
Нет, data.Count <=N

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


Кольцевые буферы много где применяются. Например, на портах, вроде, так устроено - буферы там довольно ограниченные, и они перезаписываются с начала, когда достигнут конца. Ещё я использовал кольцевой буфер на графике, который в реальном времени показывал ход эксперимента. Там и всплыла проблема WPF - окошко графика не можно отрисовать более примерно 10-15к точек в секунду. Т.е. на графике только оси координат и обозначения, которые постоянны и не меняются, и постоянно добавляющиеся данные эксперимента в виде точек, соединённых линиями - больше ничего. Если хочешь, чтобы график обновлялся хотя бы 5-8 раз в секунду, то больше 1-2к точек не показывай. Т.е. накопление данных на графике не подходило никак - пришлось воспользоваться "бегущим" графиком через кольцевой буфер (взял из поставки графика, а не сам написал). Всё равно там пришлось городить систему оптимизации данных, посылаемых на отрисовки, которая брала не все данные, а, скажем, каждое 5, и только его отрисовывало. Ну и не тупо каждое 5, а с сохранинем экстремумов, чтобы не пропускать максимальные и минимальные значения, которые важны оператору, проводящему эксперимент. Вобщем, довольно навороченная и настраеваемая система оптимизации по данным была (для всего - для приёма с устройства, для отрисовок, для расчёта и прочего).


Но это не подходит в случае, когда нужно хранить данные всего эксперимента. Тогда нужно либо сбрасывать на ROM, либо ещё куда, чтобы потом можно было достать (с соответствующим замедлением обработки, конечно).


В моём случае скорость поступления данных была в максимуме где-то около 10000 4-байтовых (int) значений в секунду. Эксперимент длится в пределе минут 5 (это с кратным запасом) - т.е. это 10000 значений * 4 байт * 60 секунд * 5 минут = 12М байт. Из-за особенностей расчёта эти данные с разными перерасчётами дублировались на разных слоях раз 10 - т.е. в сумме 120М байт в RAM. Ну и на всё остальное сколько-то памяти нужно - вобщем, в 400-500М байт я укладывался за 5 минут работы программы. Если не оставлять программу включенной постоянно, то вылетать с переполнением памяти не будет. Правда, при 10к данных в секунду у меня уже тормозил процессор и расчёт. Да и такая точность была не нужна. Там по сути много данных шло из-за того, что нужно было определять обороты в минуту, а для этого использовались метке на вращающемся теле. Меток должно было быть в пределах 50-400 (в идеале - чтобы было видно профиль работы каждого цилиндра двигателя в одном обороте, если двигатель многоцилиндровый). На реальных двигательных установках скорость вращения не превышала 10к оборотов в минуту (166 в секунду). Т.е. теоретический максимум был 33к метки (т.е. 4-байтовых данных) в секунду, но это прям вообще теоретический. Реалистичный максимум не превышал 4-5к в секунду. Да и задачи, чтобы подходило подо все возможные обороты в мире не ставилось. Т.е. всякие "формулы 1" с их 15-19 rpm и ракетные топливные турбины с их, доходящими до 100к rpm не рассматривались. Основные клиенты были - сельхозтехника, электротурбины и дизели, где обороты куда скромнее.


Если оставить за кадром всё остальное, то да.

Синхронизации и блокировки тормозят процесс работы с данными. Т.е. весь объём времени по сути делится между числом потоков, и каждый поток может работать с этими данными не больше, чем время, оставшееся от работы других потоков. А мне надо было, чтобы задержек не было, чтобы было быстро. Тут палка о двух концах - либо блокировки и синхронизации с неизбежным ожиданием очереди на работу с данными, либо одновременный доступ с осуществлением "потокобезопасности" другими способами, типа специальной организации доступа к данным, когда потоки не будут друг другу мешать. Сделать и то, и то одновременно невозможно. Разве что пойти способом, подобным рэйд-массивам в системах хранения данных - т.е. делать копии данных и возволять работать потокам с этими копиями. Но это надо делать систему синхронизации копий, а кроме того, у нас не получится чистый рэйд, т.к. физический источник будет всё равно один - общая RAM, ну и узким местом будет интерфейс доступа к этой RAM. Вобщем, выходы наверняка есть, но не тривиальные.

#38 
alex445 свой человек05.09.21 08:29
NEW 05.09.21 08:29 
в ответ alex445 05.09.21 08:17

Кстати, во всяких веб-джаваскриптовых графиках я не встретил примера постоянно обновляемого графика, способного отрисовывать по хотя бы 10к точек, соединённых линиями, хотя бы 5 раз в секунду. Т.е. это не только проблема WPF с его retained model (когда данные, передающиеся в видеокарту, не изменяются, а если надо изменять, да ещё постоянно - производительность быстро падает), а в принципе сложно сделать такой высокопроизводительный график на высокоуровненых языках с кучей абстракций. Сколько ни оптимизируй, а всё равно упрёшься в систему отрисовки. Если не сможешь в неё вмешаться, то всё - остаются оптимизации лишь в твоём коде, поставляющем данные на отрисовку.

#39 
alex445 свой человек05.09.21 08:40
NEW 05.09.21 08:40 
в ответ Бесконечный цикл 04.09.21 21:49, Последний раз изменено 05.09.21 08:53 (alex445)
Это не интерпретация - это основы. А интерпретация это как реализовано с C#, Python или других языках или либах. Поэтому я и говорю, что лучше все-таки разобраться в основах, чем как это реализовано в каком-то языке. По крайней мере когда я въезжал в тему, мне так было существенно быстрее.


Вот ссылки: Coroutine, Event_loop

Die Sache ist, что они предоставляют набор правил ("делай так и не делай так") для обращения с чёрным ящиком async/await. А вы пытаетесь туда залезть и понять суть. Да ещё суть не конкретного чёрного ящика, а целой группы похожих чёрных ящиков. А суть в конкретном ящике могут со временем заменить, не заменив правил. И тогда понимание сути устареет. Тем более, понимание сути для всей группы ящиков.


Вы - разработчик на определённых уровнях абстракций. Как устроено внутри - не ваше дело. Если вам надо знать, как устроено внутри не из-за любопутства, а чтобы понять эти абстракции, значит абстракции плохие и их нужно заменить. Ну типа как управление автомобилем (пусть это будет ручная коробка и инжектор) - у всех примерно одинаково, с небольшими нюансами на холостые ходы педалей, руля, силы нажатия. Но суть одна, и научившись ездить на одном, вы почти так же поедете на другом. А если вам надо обязательно лезть под капот или разбираться с системой впрыска конкретного инжектора, чтобы просто поехать - нафиг такие абстракции. Я не имею ввиду, скажем, идеальные профили разгона и управления в заносах - это у каждой машины своё, и чтобы использовать её по максимому, надо к ней привыкать.


Но в любом случае, async/await не заменяет нормальную работу с потоками (Thread, lock, всякие MemoryBarrier и прочее).
Ну понятно что не заменяет - это разные модели.

Новичку, да и середнячку сложно сразу понять, где заканчивается банальная асинхронность и начинается настоящая многопоточность. Многие новички пытаются писать на async/await код, который скорее выглядит как многопоточный, чем асинхронный, и проваливаются. Как я говорил, лучше в тьюториалах иметь более-менее исчерпывающий набор типичных use cases для async/await и за него сильно не выходить, чем просто вываливать на людей набор правил и делайте с ними что хотите. Создатели async/await наверняка сами прорабатывали эти use cases - для чего они вообще эти конструкции языка делают. Иначе это превращается в хождение по граблям даже для сеньёров, не то что новичков.

#40 
1 2 3 4 5 все