Search code examples
c#for-loopmauiobservablecollectionbinance-api-client

Remove an item from a continuously updating collection in Net Maui


I have a method in a viewmodel that updates an observablecollection (Indicators) every time data is sent from the Binance Api. However, if I remove the last item from that list (pressing a button on the app multiple times, or even once at the critical time), I can get an index out of range error. The program will continue only if I catch the error, but I cannot figure out a mechanism to avoid the error in the first place.

public MainViewModel()
{   
    //this command starts the Api stream from Binace.     
    UpdateAllCommand = new Command(async () => await this.UpdateAll()); 

    //this command removes the last item from the list
    RemoveIndicatorCommand = new Command(this.RemoveIndicator); 
}


    async Task UpdateAll()
    {
                var result = await       this.socketClient.UsdFuturesApi.SubscribeToMiniTickerUpdatesAsync(this.Symbol, async data =>
                {
                    //Update Indicators everytime data is streamed  
                    if (this.Indicators != null && this.Indicators.Count > 0)
                    {
                        for (int i = 0; i < this.Indicators.Count; i++)
                        {
                            if (i < this.Indicators.Count)
                            {
                                try
                                {
                                    
                                   var values = await GetIndicator(this.Indicators[i].Name, this.Indicators[i].Period);
                                   this.Indicators[i].Value = values[^1];
                                 }
                                
                                catch (Exception ex)
                                {
                                    ...
                                }
                            }
                        }
                    }

                });
        }
    }

    
    void RemoveIndicator()
    {
        if (this.Indicators.Count > 0)
        {
                this.Indicators.RemoveAt(this.Indicators.Count - 1);
        }
    }


I am aware that if I try to remove items from a collection while it's updating and looping, I can get index out of range error. But how could I safely remove it without error? I have try to use lock or semaphoreslim but with no results. My approach could be wrong all together, but please help me with a hint. Thank You!


Solution

  • Your question is about how to remove an item from a continuously updating collection and one short fix to try would be getting the last item using Linq instead of an index.

    1. The LastOrDefault() will either retrieve an item or it won't
    2. If it's null it isn't going to attempt to remove.
    3. Otherwise, the remove can be attempted because if it's no longer in the collection then it's not a range error (it just won't do anything).
    void RemoveIndicator()
    {
        if (Indicators.LastOrDefault() is MyIndicator removeMe)
        {
            Indicators.Remove(removeMe);
        }
    }
    

    And yes, your intuition to put a lock around the above block is probably a good one.


    Better still, you might want to experiment with something like this if you haven't already:

    SemaphoreSlim _sharedCriticalSection = new SemaphoreSlim(1, 1);
    async Task UpdateAll()
    {
        var result = await this.socketClient.UsdFuturesApi.SubscribeToMiniTickerUpdatesAsync(this.Symbol, async data =>
        {
            try
            {
                await _sharedCriticalSection.WaitAsync(); 
                //Update Indicators everytime data is streamed  
                if (this.Indicators != null && this.Indicators.Count > 0)
                {
                    for (int i = 0; i < this.Indicators.Count; i++)
                    {
                        if (i < this.Indicators.Count)
                        {
                            try
                            {
    
                                var values = await GetIndicator(this.Indicators[i].Name, this.Indicators[i].Period);
                                this.Indicators[i].Value = values[^1];
                            }
    
                            catch (Exception ex)
                            {
                                ...
                            }
                        }
                    }
                }
            }
            finally
            {
                _sharedCriticalSection.Release();
            }
        });
    }
    async Task RemoveIndicator()
    {
        try
        {
            await _sharedCriticalSection.WaitAsync(); 
            if (Indicators.LastOrDefault() is MyIndicator removeMe)
            {
                Indicators.Remove(removeMe);
            }
        }
        finally
        {
            _sharedCriticalSection.Release();
        }
    }