Задать вопрос
@Nimus2000

Как реализовать потоковое заполнение массива другим потоком?

Как реализовать такую задачу.

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

Вот пример, он вообще не работает как ожидаю. Сколько там недочетов. потенциальных ошибок. неверных решений?
static void Main(string[] args)
        { 
            var p = new Program();
            Task.Run(() => p.test()).Wait(1 );
            while (p.isRun)
            {
                int n; 
                n = p.lastInterputIndex == 0 ? p.N : p.lastInterputIndex; 
                Console.WriteLine("\tPtint");
                if (p.latestFrame != null)
                    for (int i = 0; i < n; i++)
                    {
                        Console.WriteLine(p.latestFrame[i]);
                    }  
                Thread.Sleep(2000); 
            }

        }


        bool isRun = false;  
        private object lockObj=new();
        private int lastInterputIndex=0;
        private int[] latestFrame; 
        private int N=20;


        private async Task test()  
        {
            await RenderLoop();
        }

        private async Task RenderLoop()
        {
            if (isRun)
                return;
            isRun = true;

            while (isRun)
            { 
                renderCts = new CancellationTokenSource();
                var token = renderCts.Token;

                var renderTask = Task.Run(() => RenderFrame(token ));
                bool complected = await Task.WhenAny(renderTask, Task.Delay(2000 )) == renderTask; 
                lock (lockObj)
                {
                    latestFrame = renderTask.Result;
                    if (complected)
                    { 
                        isRun= false;   
                    
                    }
                    else
                    { 
                        renderCts.Cancel(); // Прерываем долгий рендер        
                    }  
                } 
    
            }
        }
        private int[] RenderFrame(CancellationToken token )
        {

            int[] arr;
            lock (lockObj)
                arr =latestFrame?? new int[N];
        
            for (int i=lastInterputIndex; i<arr.Length; i++)
            {
                arr[i] = i;
                if (token.IsCancellationRequested)
                {
                    lastInterputIndex = i+1;
                    return arr;
                } 
                Thread.Sleep(500); 
            }
            lastInterputIndex = 0;
            return arr; 
        }

Я ожидаю печать по 4 элемента,(ну или 3-5 элементов) и завершение работы. Этого не просиходит.
Функция не завершается, и не печатает как ожидаю!
Иногда завершается. но не допечатывает концовку.
var renderTask = Task.Run(() => RenderFrame(token ));
      bool complected = await Task.WhenAny(renderTask, Task.Delay(2000 )) == renderTask;

Вот эта например строчка, как работает функция Task.WhenAny почему она не выполняется так как написано она должна выполнятся, Она должна вернуть первый завершившийся поток. Но она возвращает всегда не то что я ожидаю при том это даже не рандом, это именно то что мне не нужно, сколько не тестирую.
Например если я сделаю
bool complected = await Task.WhenAny(renderTask, Task.Delay( int.MaxValue )) == renderTask;
то очевидно renderTask выполнится быстрее и результат всегда true, но это не проиходит. как и обратное.

Потом программа(но уже не этот код) или поток на этой строчке может завершиться, без ошибок. И это прям смешно, стоит какой-то Console.WriteLine добавить ниже и не завершается.
Потом при дебагинге, на следующию порой строчку, не идет управление, будто зависает, но не зависает.

----------------------------------------------------------------------------------------------------------------------------------------------
Как реализовать разделение отрисовки графики, и вычисления параметров не блокирую интерфейс программы
Похожая функция есть в wpf проекте, там нужно отрисовать часть графики, и если не успела выполнится, то отрисовать то что есть, при этом все должно в минимум в 3 потоках происходить,
  • 1 поток интерфейса, туда в OnRender просто нужно вернуть буффер с прошлого кадра отрисовки.
  • 2 поток отрисовки, 1 рисует на буффер(элемент, rendertargetbitmap, массив) графику, допустим 10 кругов по 1сек каждый с задержкой
  • 3 поток, поток модели, здесь вычисляются характеристики модели, допустим пустая нагрузка, и передается на отрисовку

При том 2 и 3 потоки должны работать одновременно, отрисовывается всегда по вычисленным характиристикам пред кадра, а поток интерфейса получается на целых 2 кадра должен отставать.
  • Вопрос задан
  • 46 просмотров
Подписаться 1 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 1
@mvv-rus
Настоящий админ AD и ненастоящий программист
Сколько там недочетов. потенциальных ошибок. неверных решений?

1. У вас намешаны синхронная - в с одном потоке с блокировками (Wait, Sleep) - и асинхронная (async/await) многозадачность Теоретически, совмещать их можно, но лучше остановиться на каком-то одном подходе - легче будет. Синхронное выполнение задач (каждая - в своем потоке, который она блокирует по необходимости) - это проще, но асинхронное выполнение (при которм поток не блокируется, а освобождается, а задача планируется для продолжения после завершения операции в свободном потоке) позволяет получить больше производительности. Вот и выбирайте, что вам нужнее.
2. Для синхронизации используйте не переменные (а, к примеру, isRun у вас используется именно для этого), а объекты синхронизации. В частности, isRun следует заменить на объект синхронизации. Для синхронного выполнения лучше всего IMHO подойдет ManualResetEventSlim. для асинхронного - что-нибудь, на чем можно сделать await (например, TaskCompletionSource.Task).
3. Любой доступ к совместно используемым в разных задачах объектам(ресурсам) следует синхронизировать путем захвата связанного с ресурсом объекта исключающего доступа. Для синхронного выполнения подойдет оператор lock (вижу, что вы его уже используете). Для асихронного выполнения lock можно использовать, но - только если в его теле нет await (и компилятор вам не позволит нарушить это правило). Не пытайтесь обойти это правило, используя вместо lock другие объекты синхронизации, подразумевающие захват владения на уровне потока (Monitor, Mutex...) - компилятор всего лишь не сможет вам помешать сделать ошибку. В таком случае для синхронизации можно использовать SemaphoreSlim, но это требует понимания и аккуратности, поэтому подробности пока не пишу.
Думаю, пока этой информации к размышлению вам хватит.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы