AMcoder - javascript, python, java, html, php, sql

CancellationTokenSource-ով ալիքները, որոնց հիշողության ժամկետը լրանում է, հեռացվելուց հետո

Ամբողջական վերարտադրվող կոդը github-ում է, գործարկվողը գործարկելուց հետո հիշողությունը շուտով կթողարկվի: Կոդը հիմնականում գտնվում է AsyncBlockingQueue.cs դասում:

Հետևյալ կոդը իրականացնում է պարզ async արգելափակման հերթ.

        public async Task<T> DequeueAsync(
            int timeoutInMs = -1,
            CancellationToken cancellationToken = default)
        {
            try
            {
                using (CancellationTokenSource cts = this.GetCancellationTokenSource(timeoutInMs, cancellationToken))
                {
                    T value = await this._channel.Reader.ReadAsync(cts?.Token ?? cancellationToken).ConfigureAwait(false);
                    return value;
                }
            }
            catch (ChannelClosedException cce)
            {
                await Console.Error.WriteLineAsync("Channel is closed.");
                throw new ObjectDisposedException("Queue is disposed");
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                await Console.Error.WriteLineAsync("Dequeue failed.");
                throw;
            }
        }


        private CancellationTokenSource GetCancellationTokenSource(
            int timeoutInMs,
            CancellationToken cancellationToken)
        {
            if (timeoutInMs <= 0)
            {
                return null;
            }

            CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            cts.CancelAfter(TimeSpan.FromMilliseconds(timeoutInMs));
            return cts;
        }

Երբ օգտագործվում է այս կերպ, այն ունի հիշողության արտահոսք.

try
{
   string message = await this._inputQueue.DequeueAsync(10,cancellationToken).ConfigureAwait(false);
}
catch(OperationCanceledException){
   // timeout 
}

մուտքագրեք պատկերի նկարագրությունը այստեղ


  • Թվում է, թե դա հին թեմա է, բայց այն դեռ շփոթեցնում է ինձ և հիշողությունից դուրս է գալիս: 17.05.2021
  • Դա դիզայնով է github.com/dotnet/runtime/issues/761 19.05.2021
  • Ալիքները չեն արտահոսում, երբ ճիշտ օգտագործվում են: Ալիքը հերթ չէ, դա այլ կոնտեյներ է՝ օգտագործման տարբեր արտահայտություններով: Ձեր գրած ամբողջ կոդը, ըստ էության, պարզապես զանգ է _channel.Reader.ReadAsyncին: Մնացածը փորձում է լուծել DequeueAsync-ի գոյությամբ առաջացած խնդիրները: 19.05.2021
  • AsyncBlockingQueue-ն ինքնին հակասական է և միանշանակ հակառակը այն բանի, թե ինչ է ալիքը: Ալիքի բոլոր գործողությունները չեն արգելափակվում: Դուք կարող եք ալիքը պատկերացնել որպես AsyncQueue, միայն թե դա այդպես չէ: Շատ լավ պատճառ կա, որ կան առանձին ChannelReader և ChannelWriter դասարաններ: 19.05.2021
  • @PanagiotisKanavos Տրամաբանականորեն արգելափակելով ոչ գլխարկի տակ, ինձ անհրաժեշտ էր հերթ, որն իրեն պահում է որպես արգելափակող հերթ ասին սցենարի դեպքում: Հերթագրման ընթերցիչը նույնիսկ չեղարկված դիզայնը առաջացնում է վերը նշված խնդիրը: Խոսելով Սթիվեն Թուբի հետ, նրանք առայժմ հավատարիմ են մնում դիզայնին: 19.05.2021
  • Ինչպես պետք է: Որովհետև ալիքներն ամենևին էլ դա չեն: Ալիքները ավելի բարձր մակարդակի կառուցվածքներ են, որոնք օգտագործում են հերթեր: 19.05.2021
  • Ինչպե՞ս եք փորձում օգտագործել այս դասը: Խնդիրը կնկատեիք միայն, եթե երկար ժամանակ փորձեիք հարցում կատարել ալիքի վրա, ինչը, անկասկած, այնպես չէ, թե ինչպես է աշխատում համաժամեցված ծրագրավորումը: Հաճախորդները պետք է օգտագործեն await-ը և շարունակեն օգտագործել տվյալները, երբ դրանք հասանելի լինեն: Սպասելն այլընտրանք է քվեարկությանը 20.05.2021
  • Հիշողության ավելացման պատճառը արտահոսքը չէ: Ալիքները երաշխավորում են կարգը, ինչը նշանակում է, որ ReadAsync գործառնությունները պետք է մշակվեն իրենց կատարման հերթականությամբ: Երբ դուք կատարում եք ReadAsync զանգ, AsyncOperation օբյեկտը հերթեր է մնում, մինչև ինչ-որ բան տեղադրվի ալիքում: Երբ դուք չեղարկում եք գործողությունը, այն չի հանվում հերթից, դա հերթ է, միայն գլուխը կարող է հետաձգվել: Ինչը տեղի է ունենում միայն այն ժամանակ, երբ գրվում է նոր նյութ: Ավելի վաղ դա անելու պատճառ չկա, քանի որ ալիքները նախատեսված չեն հարցումների համար: 20.05.2021
  • Եթե ​​իսկապես ցանկանում եք հարցում անել ալիքի վրա (ինչու՞?), օգտագործեք Փորձեք կարդալ: Ինչո՞ւ ընդհանրապես դա անել, երբ await ReadAsync-ը անմիջապես կտեղեկացնի զանգահարողին, երբ նոր տվյալներ գան: Սա արգելափակող զանգ չէ: Ոչ մի թել սառեցված չէ 20.05.2021
  • Կարո՞ղ եք սահմանել SingleReader տարբերակ ալիքի համար: Երբ կա միայն մեկ ընթերցող, ալիքը չի հերթագրում համաժամեցման գործողությունները, այն կօգտագործի մեկ օրինակ, քանի որ մեկ ընթերցողը կարող է միաժամանակ կատարել միայն մեկ Read հարցում: 20.05.2021
  • խնդրում եմ բացատրեք իրական խնդիրը: Դուք փորձում եք համաժամեցված հերթ ստեղծել ReadAsync-ին ժամանակի դադարով, բայց ինչո՞ւ: Պահանջների իմացությունը կօգնի ձեր կոնկրետ խնդրի լուծում առաջարկել: Ցանկանու՞մ եք օգտագործել հարցումը (ինչու՞): Ինչու՞ հերթ դնել սպառողներին ծանուցող հերթի փոխարեն: Կա՞ մեկ կամ շատ սպառող: Եթե ​​շատ են, արդյոք նրանք պետք է ստանան տվյալներ իրենց պահանջած հերթականությամբ: Դա այն է, ինչ անում է Channel, որը ձեզ խնդիրներ է առաջացրել։ Թե՞ նորմալ է ցանկացած ակտիվ հարցում մատուցելը: Դա կարող է բարդ լինել, այդ իսկ պատճառով AsyncOperations-ն օգտագործվում է բոլոր առկախ հարցումները ներկայացնելու համար: 21.05.2021
  • @PanagiotisKanavos այս հարցն ինքնին բացատրում է github.com/dotnet/runtime/issues/761 ինձ համար ես հաղորդագրություններ եմ փոխանցում ալիքով, ինձ պետք է, որ այն պահի որպես արգելափակող հերթ, խնդրում եմ նորից չվիճել արգելափակող բառը, ինձ չի հետաքրքրում, թե ինչ կա գլխարկի տակ, պետական ​​մեքենա, iocp, epoll, kqueue . ալիքի մյուս ծայրում կա պրոցեսոր՝ հաղորդագրությունները խմբաքանակով մշակելու համար: այն սկսում է մշակվել, երբ բավականաչափ հաղորդագրություններ կան կամ ժամանակն ավարտվում է, այստեղ է հայտնվում ժամանակի չեղարկումը: Հարցում նույն օգտագործման դեպքերով մարդիկ նույն խնդիրն ունեն: 21.05.2021
  • @PanagiotisKanavos Ես խոսել եմ Սթիվեն Թուբի հետ, նա կասեր՝ կնայեն։ Ես դա լուծելու տհաճ ճանապարհ եմ գտել: Ավելի ուշ կտեղադրեմ։ 21.05.2021
  • թվում է, թե այդ ժամանակ դուք ուզում եք խմբաքանակային գործողություն, այլ ոչ թե հարցում: Ավելացրե՛ք դա հենց հարցի մեջ։ Համեմատաբար հեշտ է ստեղծել այնպիսի գործառույթ, որը կարդում է նյութերը ChannelReader-ից, դրանք պահում է օրինակ Ցուցակում և ուղարկում է ամբողջ Ցուցակը թիրախային ալիք, երբ կա՛մ հաշվարկը, կա՛մ ժամանակի վերջը: Այդ ֆունկցիային անհրաժեշտ է միայն Timer, որը պարբերաբար աշխատում է: Միայնակ անելը (խմբաքանակ ըստ հաշվարկի կամ ժամանակաշրջանի) շատ հեշտ է: Երկուսն էլ համատեղելը, որպեսզի նրանք չընկնեն միմյանց ճանապարհին, մի փոքր ավելի աշխատանք է 21.05.2021
  • Հարցն արդեն ստացել է պատասխաններ, ուստի նպատակահարմար չի լինի հարցը փոխել այնպես, որ անվավեր դառնան առկա պատասխանները: Այս հարցը վերաբերում է այն ալիքներին, որոնք արտահոսում են հիշողությունը որոշ սցենարներում (այն հատկորոշված ​​է հիշողության արտահոսք պիտակով): Եթե ​​OP-ն ուզում է գաղափարներ/առաջարկներ այն մասին, թե ինչպես իրականացնել որոշ ցանկալի ֆունկցիոնալություն, նրանք պետք է տեղադրեն նոր հարց IMHO: 21.05.2021

Պատասխանները:


1

Թարմացնել

Մեկնաբանություններից.

կա պրոցեսոր՝ հաղորդագրությունները խմբաքանակով մշակելու համար: այն սկսում է մշակվել, երբ բավականաչափ հաղորդագրություններ կան կամ ժամանակն ավարտվում է, այստեղ է հայտնվում ժամանակի չեղարկումը

Սա նշանակում է, որ այն, ինչ իրականում անհրաժեշտ է, հաղորդագրությունները խմբաքանակով խմբավորելու միջոց է և՛ ըստ քանակի, և՛ ժամանակաշրջանի: Երկուսն էլ անելը համեմատաբար հեշտ է:

Այս մեթոդի խմբաքանակները ըստ հաշվարկի: Մեթոդը հաղորդագրություններ է ավելացնում batch ցուցակին, մինչև սահմանը հասնի, ուղարկում է տվյալները ներքև և մաքրում ցուցակը.

static ChannelReader<Message[]> BatchByCount(this ChannelReader<Message> input, int count, CancellationToken token=default)
{
    var channel=Channel.CreateUnbounded();
    var writer=channel.Writer;   

    _ = Task.Run(async ()=>{
        var batch=new List<Message>(count);
        await foreach(var msg in input.ReadAllAsync(token))
        {
            batch.Add(msg);
            if(batch.Count==count)
            {
                await writer.WriteAsync(batch.ToArray());
                batch.Clear();
            }
        }
    },token)
   .ContinueWith(t=>writer.TryComplete(t.Exception));
   return channel;
}

Մի մեթոդ, որը խմբաքանակ է բաժանում ըստ ժամանակաշրջանի, ավելի բարդ է, քանի որ ժմչփը կարող է միևնույն ժամանակ հաղորդագրություն ստանալ: Interlocked.Exchange-ը փոխարինում է գոյություն ունեցող batch ցուցակը նորով և ուղարկում խմբաքանակի տվյալները ներքև: :

static ChannelReader<Message[]> BatchByPeriod(this ChannelReader<Message> input, TimeSpan period, CancellationToken token=default)
{
    var channel=Channel.CreateUnbounded();
    var writer=channel.Writer;   

    var batch=new List<Message>();
    Timer t=new Timer(async obj =>{
        var data=Interlocked.Exchange(ref batch,new List<Message>());
        writer.WriteAsync(data.ToArray());
    },null,TimeSpan.Zero,period);

    _ = Task.Run(async ()=>{
        
        await foreach(var msg in input.ReadAllAsync(token))
        {
            batch.Add(msg);
        }
    },token)
   .ContinueWith(t=>{
        timer.Dispose();
        writer.TryComplete(t.Exception);
   });
   return channel;
}

Երկուսն էլ անել, ես դեռ աշխատում եմ դրա վրա: Խնդիրն այն է, որ և՛ հաշվառման, և՛ ժամանակաչափի ժամկետի ավարտը կարող է տեղի ունենալ միաժամանակ: Վատագույն դեպքում, lock(batch)-ը կարող է օգտագործվել՝ ապահովելու համար, որ միայն շարանը կամ օղակը կարող են ուղարկել տվյալները ներքև

Բնօրինակ պատասխան

Ալիքները չեն արտահոսում, երբ ճիշտ օգտագործվում են, ինչպես ցանկացած այլ տարա: Ալիքը ասինխրոն հերթ չէ և հաստատ արգելափակող չէ: Դա շատ տարբեր կոնստրուկտ է, բոլորովին այլ արտահայտություններով: Դա ավելի բարձր մակարդակի կոնտեյներ է, որը օգտագործում է հերթեր: Շատ լավ պատճառ կա, որ կան ChannelReader և ChannelWriter առանձին դասեր:

Տիպիկ սցենարն այն է, որ հրատարակիչը ստեղծի և տիրանա ալիքին: Միայն հրատարակիչը կարող է գրել այդ ալիքին և զանգահարել Complete()ին: Channel-ը չի իրականացնում IDisposable-ը, ուստի այն չի կարող տնօրինվել: Հրատարակիչը բաժանորդներին տրամադրում է միայն ChannelReader:

Բաժանորդները տեսնում են միայն ChannelReader և կարդում են դրանից մինչև այն ավարտվի: Օգտագործելով ReadAllAsync բաժանորդը կարող է շարունակել կարդալ ChannelReader-ից մինչև այն ավարտվի:

Սա բնորոշ օրինակ է.

ChannelReader<Message> Producer(CancellationToken token=default)
{
    var channel=Channel.CreateUnbounded<Message>();
    var writer=channel.Writer;

    //Create the actual "publisher" worker
    _ = Task.Run(async ()=>{
        for(int i=0;i<100;i++)
        {
            //Check for cancellation
            if(token.IsCancellationRequested)
            {
                return;
            }
            //Simulate some work
            await Task.Delay(100);
            await writer.WriteAsync(new Message(...));          
        }
    }  ,token)
    //Complete and propagate any exceptions
    .ContinueWith(t=>writer.TryComplete(t.Exception));

    //This casts to a ChannelReader
    return channel;
}

Բաժանորդին աշխատելու համար անհրաժեշտ է միայն ChannelReader: Օգտագործելով ChannelReader.ReadAllAsync բաժանորդին անհրաժեշտ է միայն await foreach հաղորդագրությունները մշակելու համար.

async Task Subscriber(ChannelReader<Message> input,CancellationToken token=default)
{
    await foreach(var msg in input.ReadAllAsync(token))
    {
        //Use the message
    }
}

Բաժանորդը կարող է արտադրել իր սեփական հաղորդագրությունները՝ վերադարձնելով ChannelReader: Եվ այստեղ ամեն ինչ շատ հետաքրքիր է դառնում, քանի որ Subscriber մեթոդը դառնում է քայլ շղթայված քայլերի խողովակաշարում: Եթե ​​մեթոդները փոխարկենք ընդլայնման մեթոդների ChannelReader-ում, մենք հեշտությամբ կարող ենք ստեղծել մի ամբողջ խողովակաշար:

Եկեք ստեղծենք որոշ թվեր.

ChannelReader<int> Generate(int nums,CancellationToken token=default)
{
    var channel=Channel.CreateBounded<int>(10);
    var writer=channel.Writer;

    //Create the actual "publisher" worker
    _ = Task.Run(async ()=>{
        for(int i=0;i<nums;i++)
        {
            //Check for cancellation
            if(token.IsCancellationRequested)
            {
                return;
            }

            await writer.WriteAsync(i*7);  
            await Task.Delay(100);        
        }
    }  ,token)
    //Complete and propagate any exceptions
    .ContinueWith(t=>writer.TryComplete(t.Exception));

    //This casts to a ChannelReader
    return channel;
}

Այնուհետև կրկնապատկեք և քառակուսիացրեք դրանք.

ChannelReader<double> Double(this ChannelReader<int> input,CancellationToken token=default)
{
    var channel=Channel.CreateBounded<double>(10);
    var writer=channel.Writer;

    //Create the actual "publisher" worker
    _ = Task.Run(async ()=>{
        await foreach(var msg in input.ReadAllAsync(token))
        {
            await writer.WriteAsync(2.0*msg);          
        }
    }  ,token)
    //Complete and propagate any exceptions
    .ContinueWith(t=>writer.TryComplete(t.Exception));

    return channel;
}

ChannelReader<double> Root(this ChannelReader<double> input,CancellationToken token=default)
{
    var channel=Channel.CreateBounded<double>(10);
    var writer=channel.Writer;

    //Create the actual "publisher" worker
    _ = Task.Run(async ()=>{
        await foreach(var msg in input.ReadAllAsync(token))
        {
            await writer.WriteAsync(Math.Sqrt(msg));          
        }
    }  ,token)
    //Complete and propagate any exceptions
    .ContinueWith(t=>writer.TryComplete(t.Exception));

    return channel;
}

Եվ վերջապես տպեք դրանք

async Task Print(this ChannelReader<double> input,CancellationToken token=default)
{
    await foreach(var msg in input.ReadAllAsync(token))
    {
        Console.WriteLine(msg);
    }
}

Այժմ մենք կարող ենք խողովակաշար կառուցել


await Generate(100)
          .Double()
          .Square()
          .Print();

Եվ բոլոր քայլերին ավելացրեք չեղարկման նշան.

using var cts=new CancellationTokenSource();
await Generate(100,cts.Token)
          .Double(cts.Token)
          .Square(cts.Token)
          .Print(cts.Token);

Հիշողության օգտագործումը կարող է աճել, եթե մեկ քայլով հաղորդագրություններ ստացվեն ավելի արագ, քան դրանք սպառվում են երկար ժամանակ: Սա հեշտությամբ կարգավորվում է՝ օգտագործելով սահմանափակ ալիքի փոխարեն: Այս կերպ, եթե մեթոդը չափազանց դանդաղ է, բոլոր նախորդ մեթոդները պետք է սպասեն մինչև նոր տվյալներ հրապարակելը:

19.05.2021
  • Պանայոտիսը՝ օգտագործելով պրիմիտիվ ContinueWith մեթոդը ցույց է տալիս, որ օրինակը. պատրաստ չէ արտադրությանը։ Նաև, անկեղծ ասած, կարծում եմ, որ ձեր պատասխանն առանձնապես չի վերաբերում ՕՊ-ի հարցին: OP-ը փորձում է լուծել խնդիրը, ենթադրել է, որ Channel<T>-ը լավ գործիք կլինի խնդիրը լուծելու համար, և պարզել է, որ դրա օգտագործումը հանգեցրել է հիշողության անսպասելի արտահոսքի: Նրանց սովորեցնելը, թե ինչպես ճիշտ օգտագործել Channel<T>-ը, դժվար թե օգնի նրանց լուծել առկա խնդիրը: 19.05.2021
  • @TheodorZoulias Դուք աշխատել եք ալիքների հետ արտադրության մեջ: Եթե ​​անեիր, կհասկանայիր, թե ինչ է անում այդ ContinueWithը: Եվ you're using the wrong tool-ը լավագույն պատասխանն է, երբ մեկն օգտագործում է սխալ գործիք, կամ բացատրում է, թե ինչու .NET թիմը չի փոփոխում ալիքները՝ ոչ պատշաճ օգտագործման համար: Եվ Show don't tell շատ ավելի լավ է, քան you're doing it wrong ասելը 20.05.2021
  • Panagiotis Լավ, ես ասացի, որ այն պատրաստ չէ արտադրությանը, հավանաբար մի փոքր անտեղի էր: ContinueWith-ն առանց TaskScheduler-ը հստակ նշելու հետ կապված խնդիրը որևէ կոնկրետ ձևով կապված չէ ալիքների հետ: Նաև կրակի և մոռանալու առաջադրանքները որոշ չափով սխալ է թվում: Այն ստեղծում է չնկատված բացառությունների հնարավորություն, թեև, իհարկե, ձեր օրինակում այս հնարավորությունը փոքր է: Ինչ վերաբերում է ալիքներին աշխատանքի համար սխալ գործիք լինելուն, ո՞րը կարող է լինել ճիշտ գործիքը: Դուք գիտե՞ք որևէ async հերթ, որն առաջարկում է չեղարկում և ժամանակի դադար, որը չի արտահոսում հիշողությունը: 20.05.2021
  • Ես հիմա ավելի ուշադիր կարդացի ձեր խողովակաշարի առաջարկը: Եթե ​​ցանկանում եք իմանալ իմ կարծիքը, ապա ChannelReader<T>s-ի վրա հիմնված ամուր խողովակաշարի շրջանակ ստեղծելը կորած գործ է: Այս դասը միանգամյա օգտագործման չէ, ուստի այն չի կարող օգտվել շարահյուսական աջակցությունից՝ foreach հանգույցից հետո թվարկվողները հեռացնելու համար: Սա նշանակում է, որ Print օպերատորի սխալը չի ​​կարող հեշտությամբ տարածվել ետ՝ չեղյալ համարելու Generate, Double և Square օպերատորների կողմից ստեղծված Task.Run առաջադրանքները: Այս առաջադրանքները կխրվեն, դրանք չեն վերամշակվի և կմնան հիշողության մեջ, մինչև գործընթացը ավարտվի: 20.05.2021
  • @TheodorZoulias, այնուամենայնիվ, Go-ն լավ է աշխատում Channels-ի հետ: Go-ի Goroutine-ներում օգտագործվում են միայն ChannelReaders (ch-›) և ChannelWriters (‹-ch), ոչ երբեք հենց ալիքը: Պրոդյուսերին է պատկանում ալիքը և միակն է, ով թույլատրվում է գրել դրան, մնացած բոլորը ստանում են ընթերցող: Պատահական չէ, որ .NET դասը կոչվում է նույնը: Մեկնաբանությունները մի քանի սխալ ենթադրություններ են անում, ամենայն հավանականությամբ, ընդհանուր ալիքների հետ անծանոթ լինելու պատճառով 20.05.2021
  • @TheodorZoulias ստուգեք Go Concurrency Patterns. Pipelines and cancellation Go language բլոգից: Այն բացատրում է, թե ինչպես են խողովակները օգտագործվում խողովակաշարերում 20.05.2021
  • Փորձեցի կարդալ հոդվածը, բայց դժվարությամբ եմ հետևում, քանի որ ծանոթ չեմ Go լեզվին։ Ո՞րն է գորուտինի C# համարժեքը, կրակի և մոռացիր Task.Runը: Btw Ի՞նչ առումով կասեք, որ Channel<T>-ը տարբերվում է BufferBlock<T>-ից: Վերջինս թույլ է տալիս նույնքան հեշտությամբ տարբերակել գրողի և ընթերցողի միջև՝ իր ITargetBlock<T> և ISourceBlock<T> միջերեսների միջոցով: Ես հասկանում եմ, որ ալիքների վաճառքի կետը արագությունն ու արդյունավետությունն է, և խելամիտ սցենարով պոտենցիալ հսկայական հիշողության արտահոսք թույլ տալը տապալում է իր գոյության հիմնական նպատակը: 20.05.2021
  • @TheodorZoulias արտահոսք չկա: Կա վատ օգտագործում: Եթե ​​դուք 100 հազար չեղարկված հարցումներ եք դնում հերթում, որը նախատեսված է մշակվելու միայն նոր տվյալների հայտնվելու դեպքում, դա հերթի մեղքը չէ: Դուք կարդացե՞լ եք Սթիվեն Թուբի կամ Սթիվեն Գորդոնի հոդվածները Channels-ում: Հատկապես Ներքինները ChannelReader-ում բացատրել async գործողությունների մասին 20.05.2021
  • Ինչո՞ւ պետք է խորամուխ լինեմ ChannelReader-ի ներքին գործերի մեջ: Սովորելու համար, թե ինչպես գրել արտահոսող կոդ: Ասինխրոն հերթի ներդրումը լուծված խնդիր է առնվազն մեկ տասնամյակի ընթացքում, և նորի ստեղծումը, որն ավելի արդյունավետ է, բացառությամբ այն դեպքերի, երբ դա այդպես չէ, այնքան էլ իմաստ չունի: 21.05.2021
  • @TheodorZoulias՝ սխալ պատկերացումները մաքրելու համար: Այն, ինչ դուք ասում եք մինչ այժմ, ոչնչով չի տարբերվում նրանից, որ ինչ-որ մեկը մեղադրում է Microsoft-ին, քանի որ հավելվածը խնդիրներ է առաջացրել վայրկյանում 10 տվյալների բազայի միացումներ բացելուց հետո՝ առանց դրանք փակելու: 21.05.2021
  • @TheodorZoulias why should I delve, քանի որ այդպես ես գտա, որ SingleReader-ը օպտիմալացնում է AsyncOperation-ի օգտագործումը: Խնդրահարույց պատասխանների շատ կարող է բարելավվել՝ իրականում կարդալով առասպելական ձեռնարկները, հոդվածները և դասընթացները: Համոզված եմ, որ Սթիվեն Թուբը, Դեյվիդ Ֆաուլերը, Մարկ Գրավելը, Սթիվ Գորդոնը ապուշ չեն: Նրանք արտադրում են շատ արագ ծրագրեր: Ինչո՞ւ ենթադրել, որ նրանք հիմար բան են արել: Ավելի տրամաբանական է ենթադրել, որ մենք չենք հասկանում, թե ինչպես են օգտագործվում իրերը 21.05.2021
  • @TheodorZoulias, եթե ցանկանում եք հասկանալ CSP պարադիգմը և ինչպես պետք է օգտագործվեն Channels-ը և TPL Dataflow-ը, սովորեք Go, կարդացեք խողովակաշարի բլոգի գրառումը և օրինաչափությունները գրքում Համաժամությունը Go-ում: CSP-ն շատ տարբեր աշխատանքի եղանակ է: Երկուսն էլ DataFlow-ը և Channels-ը CSP կառուցվածքներ են, այլ ոչ թե աշխատողներ կամ գործակալներ: Կան նաև մի քանի հանդիպումներ և կոնֆերանսներ TPL DataFlow-ի վերաբերյալ, որոնք կօգնեն պարզել շփոթությունը 21.05.2021
  • Panagiotis նույնիսկ ամենախելացի մարդիկ կարող են գրել խելագարված կոդ, կամ չկարողանան կանխատեսել, թե իրենց ստեղծած գործիքները ինչպես կօգտագործվեն այլ մարդկանց կողմից, կամ էլ ծույլ կդառնան, երբ խոսքը վերաբերում է իրենց բարդ ալգորիթմների վարքագծի փաստագրմանը: Ինչևէ, ես տեսնում եմ, որ դուք շատ կրքոտ եք պաշտպանում ChannelReader<T>.ReadAsync մեթոդի ստատուս քվոն, ուստի չեմ երկարացնի վեճը։ Կարծում եմ, որ մենք երկուսս էլ հստակ ասել ենք մեր կետերը: 21.05.2021
  • @PanagiotisKanavos խնդիրը միանգամայն ակնհայտ է, սահմանափակված ալիքի սկզբնական կոդից var reader = new AsyncOperation<T>(parent._runContinuationsAsynchronously | cancellationToken.CanBeCanceled, cancellationToken); parent._blockedReaders.EnqueueTail(reader); return reader.ValueTaskOfT; ընթերցողը հերթագրվում է նույնիսկ այն չեղարկված: 21.05.2021
  • @HooYao այն չեղարկվելուց հետո հեռացված չէ հերթից: Որովհետև դա հերթ է: Ինչն անհրաժեշտ է կարդալու կարգը պահպանելու համար։ Կրկին, դա միանգամայն խելամիտ է, հաշվի առնելով, թե ինչի համար է Channel-ը. կամուրջ հրատարակիչների և սպառողների միջև՝ օգտագործելով pull մոդելը, այլ ոչ թե անկախ հավաքածու՝ հարցումներով: Դիզայներներն ամեն ինչ արեցին, որպեսզի դժվար դարձնեն ալիքը որպես մեկ կոնտեյներ օգտագործելը: Բացի այդ, ձեր իրական խնդիրն արդեն լուծվում է տարբեր գրադարանների կողմից: Ինչը, եթե չարաշահվի, կհանգեցնի նաև հիշողության օգտագործման ավելացման 24.05.2021

  • 2

    Ես կարողացա վերարտադրել ձեր դիտարկած խնդիրը։ Դա ակնհայտորեն թերություն է Ալիքների գրադարանում: IMHO. Ահա իմ ռեպո.

    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Threading.Channels;
    using System.Threading.Tasks;
    using System.Threading.Tasks.Dataflow;
    
    public static class Program
    {
        public static async Task Main()
        {
            var channel = Channel.CreateUnbounded<int>();
            var bufferBlock = new BufferBlock<int>();
            var asyncCollection = new Nito.AsyncEx.AsyncCollection<int>();
            var mem0 = GC.GetTotalMemory(true);
            int timeouts = 0;
            for (int i = 0; i < 10; i++)
            {
                var stopwatch = Stopwatch.StartNew();
                while (stopwatch.ElapsedMilliseconds < 500)
                {
                    using var cts = new CancellationTokenSource(1);
                    try
                    {
                        await channel.Reader.ReadAsync(cts.Token);
                        //await bufferBlock.ReceiveAsync(cts.Token);
                        //await asyncCollection.TakeAsync(cts.Token);
                    }
                    catch (OperationCanceledException) { timeouts++; }
                }
                var mem1 = GC.GetTotalMemory(true);
                Console.WriteLine($"{i + 1,2}) Timeouts: {timeouts,5:#,0},"
                    + $" Allocated: {mem1 - mem0:#,0} bytes");
            }
        }
    }
    

    Արդյունք:

     1) Timeouts:   124, Allocated: 175,664 bytes
     2) Timeouts:   250, Allocated: 269,720 bytes
     3) Timeouts:   376, Allocated: 362,544 bytes
     4) Timeouts:   502, Allocated: 453,264 bytes
     5) Timeouts:   628, Allocated: 548,080 bytes
     6) Timeouts:   754, Allocated: 638,800 bytes
     7) Timeouts:   880, Allocated: 729,584 bytes
     8) Timeouts: 1,006, Allocated: 820,304 bytes
     9) Timeouts: 1,132, Allocated: 919,216 bytes
    10) Timeouts: 1,258, Allocated: 1,011,928 bytes
    

    Փորձեք այն Fiddle-ում:

    Մեկ գործողության ընթացքում արտահոսում է մոտ 800 բայթ, ինչը բավականին տհաճ է: Հիշողությունը վերականգնվում է ամեն անգամ, երբ ալիքում նոր արժեք է գրվում, ուստի զբաղված ալիքի համար այս դիզայնի թերությունը չպետք է խնդիր լինի: Բայց մի ալիքի համար, որը ժամանակ առ ժամանակ արժեքներ է ստանում, սա կարող է լինել ցուցադրական:

    Կան այլ ասինխրոն հերթերի իրականացումներ, որոնք չեն տառապում նույն խնդրից: Կարող եք փորձել մեկնաբանել await channel.Reader.ReadAsync(cts.Token); տողը և չմեկնաբանել ստորև նշված երկու տողերից որևէ մեկը: Դուք կտեսնեք, որ և՛ BufferBlock<T>< /a> TPL Dataflow գրադարանը և AsyncCollection<T> Nito.AsyncEx.Coordination փաթեթից, թույլ տվեք ասինխրոն որոնում հերթը՝ ընդմիջումով, առանց հիշողության արտահոսքի։

    20.05.2021
  • Դա ամենևին էլ թերություն չէ: Սա սխալ օգտագործում է։ Ալիքները երաշխավորում են գործողությունների հերթականությունը, այնպես որ, երբ ReadAsync զանգ է կատարվում, AsyncOperation օբյեկտը հերթագրվում է: Այս օբյեկտները փակվում են միայն այն ժամանակ, երբ տեղադրվում է նոր նյութ: Օգտագործելով հարցումը, կոդը հերթագրում է տոննա AsyncOperation օբյեկտների, որոնք երբեք չեն հերթագրվում: Դա թերություն չէ, քանի որ ալիքը նախատեսված չէ ReadAsync-ով հարցում անցկացնելու համար: Դա անելու պատճառ չկա. հաճախորդը անմիջապես կտեղեկացվի, երբ նոր տվյալները գան: Հարցման համար պետք է օգտագործել TryRead 20.05.2021
  • @Panagiotis ալիքը նախատեսված չէ ReadAsync-ի հետ հարցում անցկացնելու համար: Որտե՞ղ եք գտնում այս տեղեկատվությունը փաստաթղթերում: ReadAsync ընդունում է ընտրովի CancellationToken, և ոչ մի տեղ չի ասում, որ եթե չեղարկեք նշանը, ալիքը հիշողությունից կթողնի: Այս մեթոդը կամ չպետք է ընդունի նշան, կամ չպետք է արտահոսքի, կամ պետք է փաստաթղթավորվի, որ արտահոսք է: Microsoft-ը գիտակցաբար թույլ տալ, որ պատահական մարդիկ անսպասելիորեն ընկնեն ձախողման փոսը, առանց նրանց ակնհայտ սխալ բան անելու, բավականին վատ IMHO է: 20.05.2021
  • Եթե ​​ալիքը ստեղծեք new UnboundedChannelOptions{SingleReader=true}-ով հիշողության օգտագործումը չի փոխվում, քանի որ կարիք չկա կարդալու հարցումները հերթագրելու: Մեկ ընթերցողը կարող է միաժամանակ կատարել միայն մեկ ընթերցման հարցում 20.05.2021
  • CancellationToken-ը նշանակում է, որ մեթոդը կարող է չեղարկվել: Դա ոչ ժամանակացույց է, ոչ էլ դրոշ, որը ասում է, որ մեթոդը կարող է օգտագործվել հարցումների համար: Microsoft knowingly letting random folks fall unexpectedly in the pit of failure ոչ, սա բացահայտ ջանքեր պահանջեց դասը չարաշահելու համար: Channels-ում կան մի քանի հոդվածներ Սթիվեն Թուբի, Սթիվ Գորդոնի և այլոց կողմից: SignalR-ի փաստաթղթերը ցույց են տալիս, թե ինչպես կարելի է օգտագործել ալիքները՝ հաճախորդներին հոսելու համար: 20.05.2021
  • @Panagiotis լավ գտա. Այսպիսով, new UnboundedChannelOptions() { SingleReader = true }-ը OP-ի խնդրի լուծումն է: Ձեր պատասխանի մեջ կավելացնե՞ք, ես էլ իմ պատասխանի մեջ ավելացնե՞մ։ 20.05.2021
  • @Panagiotis Microsoft-ը գովազդում ալիքները որպես տվյալների կառուցվածք, որն օգտագործվում է արտադրված տվյալները պահելու համար, որպեսզի սպառողը առբերի: Սա իմ աչքով նշանակում է, որ դա ընդհանուր օգտագործման գործիք է արտադրողի/սպառողի խնդիրները լուծելու համար: Ես ոչ մի տեղ չեմ տեսնում, որ դա գործիք է, որը պետք է օգտագործվի խիստ օրինաչափության համաձայն, և այս օրինակին չհետևելը հանգեցնում է բոլոր դժոխքի կորստի: Իսկապե՞ս լավ կլինեիք, եթե բոլոր ներկառուցված API-ները, որոնք ընդունում են նշաններ, հիշողություն արտահոսեին, եթե չփաստագրված օրինակին չհետևեն: 20.05.2021
  • @TheodorZoulias միայնակ ընթերցողը չի լուծում իրավիճակը, իմ վերարտադրվող ցուցադրությունում հիշողությունը պայթում է առաջվա պես 21.05.2021
  • @HooYao, ապա շատ վատ: Դուք մտածե՞լ եք Channel<T>-ից BufferBlock<T>ի կամ այլ այլընտրանքի անցնելու մասին: 21.05.2021
  • @TheodorZoulias, շնորհակալություն, ես կարող եմ անցնել BufferBlock-ին, այն աշխատում է ինչպես սպասվում էր: Ես գնահատում եմ դրա կատարումը, դա իմ դեպքում չպետք է որևէ տարբերություն ունենա։ Ես իրականում տհաճ լուծում ստացա՝ կեղծ հաղորդագրություն ուղարկելով ալիքին, երբ ժամանակի վերջնաժամկետը չեղարկվի, դա նվազագույն ազդեցություն ունի, բայց BufferBlock-ը շատ ավելի մաքուր տեսք ունի: 22.05.2021
  • @HooYao այո, ես չեմ ակնկալում, որ դուք կունենաք կատարողական խնդիրներ BufferBlock<T>-ի հետ: Դա մի փոքր ավելի հին տեխնոլոգիա է, այն ավելի վաղ է ValueTasks-ից, ուստի չի օգտվում դրանցից, բայց հակառակ դեպքում դա հիանալի գործիք է: Եթե ​​ցանկանում եք բացահայտել դրա բովանդակությունը որպես IAsyncEnumerable<T>, կա ToAsyncEnumerable<T> ընդլայնման մեթոդ այստեղ: 22.05.2021

  • 3

    Ես այնքան զբաղված էի բուն խնդրի տեխնիկական մանրամասներով, ես մոռացել էի, որ խնդիրն արդեն լուծված է գրեթե առանց ներբեռնման:

    Մեկնաբանություններից թվում է, թե իրական հարցը հետևյալն է.

    կա պրոցեսոր՝ հաղորդագրությունները խմբաքանակով մշակելու համար: այն սկսում է մշակվել, երբ բավականաչափ հաղորդագրություններ կան կամ ժամանակն ավարտվում է, այստեղ է հայտնվում ժամանակի չեղարկումը

    Սա տրամադրված է Բուֆեր ReactiveX.NET, որը ստեղծվել է նույն թիմի կողմից, որը ստեղծում է System.Linq.Async :

    ChannelReader<Message> reader=_channel;
    
    IAsyncEnumerable<IList<Message>> batchItems = reader.ReadAllAsync(token)
                                                  .ToObservable()
                                                  .Buffer(TimeSpan.FromSeconds(30), 5)
                                                  .ToAsyncEnumerable();
    
    await foreach(var batch in batchItems.WithCancellation(token))
    {
     ....
    }
    

    Այս զանգերը կարող են վերածվել ընդլայնման մեթոդի, ուստի DequeueAsync-ի փոխարեն հարցի դասը կարող է ունենալ BufferAsync կամ GetWorkItemsAsync մեթոդ.

    public IAsyncEnumerable<T[]> BufferAsync(
                TimeSpan timeSpan,
                int count,
                CancellationToken cancellationToken = default)
    {
        return _channel.Reader.BufferAsync(timeSpan,count,cancellationToken);
    }
    

    ToObservable-ը և ToAsyncEnumerable-ը տրամադրվում են System.Linq.Async-ի կողմից և փոխակերպվում են IAsyncEnumerable-ի և IObservable, ինտերֆեյսը, որն օգտագործվում է ReactiveX.NET-ի կողմից:

    Բուֆեր տրամադրվում է System.Reactive և բուֆերացնում է տարրը ըստ քանակի կամ կետի, նույնիսկ թույլ տալով համընկնող հաջորդականությունները:

    Մինչ LINQ-ը և LINQ to Async-ը տրամադրում են հարցումների օպերատորներ օբյեկտների վրա, Rx.NET-ն անում է նույնը իրադարձությունների ժամանակի վրա հիմնված հոսքերի նկատմամբ: Հնարավոր է ժամանակի ընթացքում համախմբել իրադարձությունները, բուֆերացնել իրադարձություններն ըստ ժամանակի, դրանք կարգավորել և այլն: Օրինակները փաստաթղթի (ոչ պաշտոնական) էջում Բուֆեր ցույց է տալիս, թե ինչպես ստեղծել համընկնող հաջորդականություններ (օրինակ՝ լոգարիթմական պատուհաններ): Նույն էջը ցույց է տալիս, թե ինչպես Sample-ը կամ Throttle-ը կարող են օգտագործվել արագ իրադարձությունների հոսքերը ճնշելու համար՝ տարածելով միայն վերջին իրադարձությունը տվյալ ժամանակահատվածում:

    Rx-ն օգտագործում է push մոդելը (նոր իրադարձությունները հրվում են բաժանորդներին), մինչդեռ IAsyncEnumerable-ը, ինչպես IEnumerable-ը, օգտագործում է ձգողական մոդել: ToAsyncEnumerable()-ը կպահի տարրերը, քանի դեռ դրանք չեն պահանջվում, ինչը կարող է հանգեցնել խնդիրների, եթե ոչ ոք չլսի:

    Այս մեթոդներով կարելի է նույնիսկ ընդլայնման մեթոդներ ստեղծել հրատարակիչներին բուֆերացնելու կամ շնչափողելու համար.

        //Returns all items in a period
        public static IAsyncEnumerable<IList<T>> BufferAsync<T>(
            this ChannelReader<T> reader, 
            TimeSpan timeSpan, 
            int count,
            CancellationToken token = default)
        {
            return reader.ReadAllAsync(token)
                .ToObservable()
                .Buffer(timeSpan, count)
                .ToAsyncEnumerable();
        }
            
            
        //Return the latest item in a period
        public static IAsyncEnumerable<T> SampleAsync<T>(
            this ChannelReader<T> reader, 
            TimeSpan timeSpan,
            CancellationToken token = default)
        {
            return reader.ReadAllAsync(token)
                .ToObservable()
                .Sample(timeSpan)
                .ToAsyncEnumerable();
        }
    
    24.05.2021
  • Rx-ն օգտագործում է push մոդել (նոր իրադարձությունները ուղարկվում են բաժանորդներին), մինչդեռ IAsyncEnumerable-ը, ինչպես IEnumerable-ը, օգտագործում է pull մոդելը: ToAsyncEnumerable()-ը կպահի տարրերը, մինչև դրանք պահանջվեն, ինչը կարող է հանգեցնել խնդիրների, եթե ոչ ոք չի լսում: ‹== Դա այս լուծման լուրջ թերությունն է և այն չի շտկվում: Ես ավելի ուշ կհրապարակեմ խնդրի ճիշտ լուծումը՝ որպես պատասխան այս հարցը. 24.05.2021
  • @TheodorZoulias՝ ամեն ինչ կոտրված ենթադրելու փոխարեն, համարեք, որ գործիքները ստեղծվել են կոնկրետ աշխատանքներ լուծելու համար: Դուք չեք կարող օգտագործել մուրճերը պտուտակներով: Իսկ զուգահեռ/համաժամանակյա/ասինկ մշակումը հսկայական տարածք է, որը չի կարող ծածկվել մեկ ծրագրավորման պարադիգմով, առավել եւս մեկ տեխնիկայով: Հիշեք, թե ինչպես էիք պատասխանում Semaphores-ի հետ յուրաքանչյուր հարցին՝ նախքան ActionBlock-ը հայտնաբերելը: Այս բաներից ոչ մեկը նոր չէ, մենք օգտագործում ենք 1970-ականներին ստեղծված հասկացություններ և տեխնիկա: .NET-ն ընդգրկում է դրանցից շատ և նույնիսկ թույլ է տալիս համատեղել դրանք: Դուք պետք է իմանաք, թե ինչ գործիք երբ օգտագործել: 24.05.2021
  • @TheodorZoulias բացի այդ, տարրերը կպահվեն ինչ-որ տեղ: Կամ ալիքում, կամ ToAsyncEnumerable-ի կողմից ստեղծված ConcurrentHueue-ում: Անկախ նրանից, թե ինչ հերթ կամ լուծում է օգտագործվում, հրապարակված տարրերը պետք է գնան ինչ-որ տեղ, մինչև դրանք սպառվեն: Եթե ​​հրատարակիչը ուղարկում է 500 նյութ, և ոչ ոք չի լսում, այդ 500 նյութն այսպես թե այնպես պետք է մնա հիշողության մեջ։ 24.05.2021
  • Panagiotis, եթե մեկ սպառողն այնքան արագ չէ, որ կարողանա հաղթահարել մուտքային հաղորդագրությունների հոսքը, OP-ը կարող է ավելացնել ավելի շատ սպառողներ: Յուրաքանչյուր սպառող պետք է ալիքից վերցնի այնքան հաղորդագրություն, որքան կարող է մշակել, և ոչ ավելին: Այս պատասխանի լուծումը կհանգեցնի նրան, որ յուրաքանչյուր սպառող ագահորեն կցամաքեցնի ալիքը և չմշակված հաղորդագրությունները կդնի թաքնված հերթի մեջ՝ ներմուծելով ավելի շատ ուշացում, քան այն, ինչ OP-ը պատրաստ կլիներ ընդունել: Այսպիսով, այս լուծումը թերի է, և դրա թերությունն ապացուցելի է: Եվ ես բարի լինելն ու այս թերությունը չմատնանշելը երկարաժամկետ հեռանկարում ոչ մեկին օգուտ չի բերի: 24.05.2021
  • @TheodorZoulias դուք բաց եք թողնում կետը: Buffer, «Նմուշը» և Throttle-ը ձեր նկարագրածը կարգավորելու միջոց է՝ հավաքելով/չեղարկելով տվյալները, որպեսզի սպառողները կարողանան միաժամանակ աշխատել խմբաքանակի վրա: Ամեն դեպքում, ոչինչ չի խանգարում բազմաթիվ սպառողների օգտագործմանը: Բացի այդ, OP-ի հարցն այն էր, թե ինչպես ստեղծել տվյալների խմբաքանակ, այլ ոչ թե ինչպես օգտագործել բազմաթիվ սպառողներ: Դա չնչին է ChannelReader-ի հետ կապված. պարզապես փոխանցեք այն բոլոր սպառողներին: Թայմաութների կարիք չի լինի 24.05.2021
  • Panagiotis the OP-ն ունի հաղորդագրությունների աղբյուր, և նրանք ցանկանում են դրանք մշակել խմբաքանակով և սահմանափակ ուշացումով: Դա խնդրի տիրույթն է: Ես չեմ կորցնում էությունը՝ կենտրոնանալով խնդրի տիրույթի վրա։ Ես կբացակայեի, եթե փորձեի փոխել խնդրի տիրույթը, որպեսզի այն համապատասխանի այն գործիքներին, որոնք ես ուզում եմ օգտագործել: Հնարավոր է, որ ես դա արել եմ այլ դեպքերում, բայց ոչ այս մեկում: Ես կցանկանայի ձեզ խնդրել, որ դուք նույնպես կենտրոնանաք խնդրի վրա: Կամ նշեք Sample, Throttle etc օպերատորների կոնկրետ օգտագործումը, որոնք լուծում են այս խնդիրը, կամ ընդհանրապես չնշեք դրանք: 24.05.2021
  • Նոր նյութեր

    Օգտագործելով Fetch Vs Axios.Js-ը՝ HTTP հարցումներ կատարելու համար
    JavaScript-ը կարող է ցանցային հարցումներ ուղարկել սերվեր և բեռնել նոր տեղեկատվություն, երբ դա անհրաժեշտ լինի: Օրինակ, մենք կարող ենք օգտագործել ցանցային հարցումը պատվեր ներկայացնելու,..

    Տիրապետել հանգստության արվեստին. մշակողի ուղեցույց՝ ճնշման տակ ծաղկելու համար
    Տիրապետել հանգստության արվեստին. մշակողի ուղեցույց՝ ճնշման տակ ծաղկելու համար Ինչպե՞ս հանգստացնել ձեր միտքը և աշխատեցնել ձեր պրոցեսորը: Ինչպես մնալ հանգիստ և զարգանալ ճնշման տակ...

    Մեքենայի ուսուցում բանկային և ֆինանսների ոլորտում
    Բարդ, խելացի անվտանգության համակարգերը և հաճախորդների սպասարկման պարզեցված ծառայությունները բիզնեսի հաջողության բանալին են: Ֆինանսական հաստատությունները, մասնավորապես, պետք է առաջ մնան կորի..

    Ես AI-ին հարցրի կյանքի իմաստը, այն ինչ ասում էր, ցնցող էր:
    Այն պահից ի վեր, երբ ես իմացա Արհեստական ​​ինտելեկտի մասին, ես հիացած էի այն բանով, թե ինչպես է այն կարողանում հասկանալ մարդկային նորմալ տեքստը, և այն կարող է առաջացնել իր սեփական արձագանքը դրա..

    Ինչպես սովորել կոդավորումը Python-ում վագրի պես:
    Սովորելու համար ծրագրավորման նոր լեզու ընտրելը բարդ է: Անկախ նրանից, թե դուք սկսնակ եք, թե առաջադեմ, դա օգնում է իմանալ, թե ինչ թեմաներ պետք է սովորել: Ծրագրավորման լեզվի հիմունքները, դրա..

    C++-ի օրական բիթ(ե) | Ամենաերկար պալինդրոմային ենթաշարը
    C++ #198-ի ամենօրյա բիթ(ե), Ընդհանուր հարցազրույցի խնդիր. Ամենաերկար պալինդրոմային ենթատող: Այսօր մենք կանդրադառնանք հարցազրույցի ընդհանուր խնդրին. Ամենաերկար palindromic substring...

    Kydavra ICAReducer՝ ձեր տվյալների ծավալայինությունը նվազեցնելու համար
    Ի՞նչ է ICAReducer-ը: ICAReducer-ն աշխատում է հետևյալ կերպ. այն նվազեցնում է նրանց միջև բարձր փոխկապակցված հատկանիշները մինչև մեկ սյունակ: Բավականին նման է PCAreducer-ին, չնայած այն..