简体   繁体   中英

ArgumentOutOfRangeException when accessing a Collection, even though index is not out of range?

I have an application which processes images through a series of image filters in one thread, and displays the images in a UI on another thread. The simplified image filter class looks like this:

// This class represents a filter operation on the GPU.
// Process() is repeatedly called by the filter processing thread.
class ShaderFilterBase : IFilter
{
    // This variable holds the result of the image operation.
    private ImageInfo resultImageInfo;

    // Indicates whether the image has been updated by a call to Render()
    bool hasChanged = true;

    public ICollection<ImageInfo> LastResults
    {
        get
        {
            ICollection<ImageInfo> results = new Collection<ImageInfo>();
            results.Add(GetEffectResult());
            return results;
        }
    }

    public ICollection<ImageInfo> Process(ICollection<ImageInfo> images)
    {
        // We only process the image if we have exactly one image.
        // If more than one image has to be processed by the GPU, this method
        // should be overridden in a derived class.
        if (images.Count == 1)
        {
            ImageInfo firstImage = images.First();

            // If the supplied image is already a shader resource on the GPU,
            // we don't need to upload the texture to the GPU again. We just
            // set the output texture of the supplied image to the input texture
            // of the current image.
            if (firstImage.IsShaderResource)
                SetResource(firstImage.ShaderResourceView);
            else
                UploadInputTexture(firstImage);

            Render();

            firstImage.ShaderResourceView = OutputShaderResourceView;
        }

        return images;
    }

    public virtual void Render()
    {
        Monitor.Enter(this);

        // Perform texture operations on the GPU            

        hasChanged = true;

        Monitor.Exit(this);
    }

    public ImageInfo GetEffectResult()
    {
        Monitor.Enter(this);

        if (hasChanged)
        {
            // Download image from GPU and store it in resultImageInfo

            hasChanged = false;
        }

        Monitor.Exit(this);

        return resultImageInfo;
    }
}

Furthermore, I have various derived classes which differ by the HLSL shader program to be executed on the GPU. The image processing thread iterates over a collection of those filter instances. GetEffectResult() is only called when the image needs to be downloaded from video memory to system memory. I use a Monitor.Enter(this), because each filter instance is guaranteed to exist only once in the filter chain.

To set up and configure the filters, I have a UI which displays the output of every filter in the filter chain. The filter instances are encapsulated by filter models, which are consumed by a WPF UI.

internal abstract class FilterModelBase : DependencyObject
{
    private WriteableBitmap preview;

    private static readonly DependencyPropertyKey PreviewPropertyKey = DependencyProperty.RegisterReadOnly("Preview",
        typeof(ImageSource), typeof(FilterModelBase), new PropertyMetadata());

    // The WPF window contains an Image control, which binds to this property.
    public static readonly DependencyProperty PreviewProperty = PreviewPropertyKey.DependencyProperty;

    public ImageSource Preview
    {
        get { return (ImageSource)GetValue(PreviewProperty); }
        private set { SetValue(PreviewPropertyKey, value); }
    }

    // The underlying filter.
    public IFilter Filter
    {
        get { return this.filter; }
    }

    protected FilterModelBase(IEventAggregator eventAggregator, IFilter filter)
    {
        Check.NotNull(filter, "filter");
        this.EventAggregator = eventAggregator;

        this.filter = filter;
    }

    // Updates the filter output preview.
    public virtual void Refresh(Dispatcher dispatcher)
    {
        if (!dispatcher.CheckAccess())
            dispatcher.Invoke(new Action(() => Refresh(dispatcher)));
        else
        {
            ImageInfo filterImage = null;

            Monitor.Enter(this.filter);

            if (this.filter != null && this.filter.LastResults.Count > 0)
                filterImage = this.filter.LastResults.ElementAtOrDefault(0);

            if (filterImage != null)
            {
                this.preview.WritePixels(new Int32Rect(0, 0, filterImage.Width, filterImage.Height),
                    filterImage.ImageBytes, filterImage.Width * filterImage.Channels, 0);
            }

            Monitor.Exit(this.filter);
        }
    }

The Refresh() method of each filter model instance is repeatedly called by the UI thread via a Timer.

Every now and then, I get an ArgumentOutOfRangeException on the following line:

filterImage = this.filter.LastResults.ElementAtOrDefault(0);

However, when I inspect the LastResults property, it contains one element, just like it should. How is it possible that an ArgumentOutOfRangeException is raised, even though the debugger says that the Collection does, in fact, contain exactly one item and I always access the first item in the collection?

The property is re-evaluated by the debugger after the exception after has been thrown. Probably the element has been inserted by another thread while the visual studio was halting everything for the debugger.

HTH

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM